@fluenti/next 0.3.4 → 0.4.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,8 +10,34 @@ export interface ClientI18nProviderProps {
10
10
  children: ReactNode;
11
11
  }
12
12
  /**
13
- * Client-side I18nProvider wrapper.
14
- * Used internally by I18nProvider to hydrate client components.
13
+ * Client-side I18nProvider wrapper for Next.js App Router.
14
+ *
15
+ * Wraps `@fluenti/react`'s `I18nProvider` with the `'use client'` directive,
16
+ * enabling hydration of client components in a Server Component tree.
17
+ * Re-exported as `I18nProvider` from `@fluenti/next/provider`.
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * // app/layout.tsx
22
+ * import { I18nProvider } from '@fluenti/next/provider'
23
+ * import en from '../locales/compiled/en.js'
24
+ *
25
+ * export default function RootLayout({ children }: { children: React.ReactNode }) {
26
+ * return (
27
+ * <html>
28
+ * <body>
29
+ * <I18nProvider
30
+ * locale="en"
31
+ * fallbackLocale="en"
32
+ * messages={{ en }}
33
+ * >
34
+ * {children}
35
+ * </I18nProvider>
36
+ * </body>
37
+ * </html>
38
+ * )
39
+ * }
40
+ * ```
15
41
  */
16
42
  export declare function ClientI18nProvider({ locale, fallbackLocale, messages, fallbackChain, dateFormats, numberFormats, children, }: ClientI18nProviderProps): import("react/jsx-runtime").JSX.Element;
17
43
  //# sourceMappingURL=client-provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-provider.d.ts","sourceRoot":"","sources":["../src/client-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAEhG,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,WAAW,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,cAAc,EACd,QAAQ,EACR,aAAa,EACb,WAAW,EACX,aAAa,EACb,QAAQ,GACT,EAAE,uBAAuB,2CAazB"}
1
+ {"version":3,"file":"client-provider.d.ts","sourceRoot":"","sources":["../src/client-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAEhG,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,WAAW,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,cAAc,EACd,QAAQ,EACR,aAAa,EACb,WAAW,EACX,aAAa,EACb,QAAQ,GACT,EAAE,uBAAuB,2CAazB"}
@@ -1 +1 @@
1
- {"version":3,"file":"generate-server-module.d.ts","sourceRoot":"","sources":["../src/generate-server-module.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAanD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,oBAAoB,GAC3B,MAAM,CA6QR"}
1
+ {"version":3,"file":"generate-server-module.d.ts","sourceRoot":"","sources":["../src/generate-server-module.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAanD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,oBAAoB,GAC3B,MAAM,CAgRR"}
package/dist/index.cjs CHANGED
@@ -4,6 +4,7 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require
4
4
  // @ts-nocheck
5
5
  import { createElement } from 'react'
6
6
  import { I18nProvider } from '@fluenti/react'
7
+ import { interpolate as __interpolate } from '@fluenti/core/internal'
7
8
  ${l.map(e=>`import ${e.replace(/[^a-zA-Z0-9]/g,`_`)} from '${b}/${e}'`).join(`
8
9
  `)}
9
10
 
@@ -12,7 +13,7 @@ const __dateFormats = ${g===void 0?`undefined`:JSON.stringify(g)}
12
13
  const __numberFormats = ${_===void 0?`undefined`:JSON.stringify(_)}
13
14
 
14
15
  export function ClientI18nProvider({ locale, fallbackLocale, fallbackChain, children }) {
15
- return createElement(I18nProvider, { locale, fallbackLocale, messages: __allMessages, fallbackChain, dateFormats: __dateFormats, numberFormats: __numberFormats }, children)
16
+ return createElement(I18nProvider, { locale, fallbackLocale, messages: __allMessages, fallbackChain, dateFormats: __dateFormats, numberFormats: __numberFormats, interpolate: __interpolate }, children)
16
17
  }
17
18
  `),u((0,n.resolve)(i,`client-provider.d.ts`),`// Auto-generated by @fluenti/next — do not edit
18
19
  import type { ReactNode, ReactElement } from 'react'
@@ -56,10 +57,12 @@ export declare function ClientI18nProvider(props: {
56
57
  // @ts-nocheck
57
58
  import { createServerI18n } from '@fluenti/react/server'
58
59
  import { createElement } from 'react'
60
+ import { interpolate as __interpolate } from '@fluenti/core/internal'
59
61
  ${C?`${C}\n`:``}
60
62
  const __locales = ${T}
61
63
 
62
64
  const serverI18n = createServerI18n({
65
+ interpolate: __interpolate,
63
66
  loadMessages: async (locale) => {
64
67
  switch (locale) {
65
68
  ${x}
@@ -172,7 +175,7 @@ export const cookieName = '${v}'
172
175
  export declare const locales: string[]
173
176
  export declare const sourceLocale: string
174
177
  export declare const cookieName: string
175
- `),s}function f(e){return e.replace(/\\/g,`\\\\`).replace(/'/g,`\\'`)}function p(e){return e.split(`\\`).join(`/`)}async function m(e){if(e.compileOnly)try{let{runCompile:t}=(0,s.createRequire)((0,n.join)(e.cwd,`package.json`))(`@fluenti/cli`);await t(e.cwd),console.log(`[fluenti] Compiling... done`),e.onSuccess?.();return}catch(t){let n=t instanceof Error?t:Error(String(t));if(e.throwOnError)throw n;console.warn(`[fluenti] Compile failed:`,n.message),e.onError?.(n);return}let t=null;try{t=(0,s.createRequire)((0,n.join)(e.cwd,`package.json`))(`@fluenti/cli`)}catch{}if(t)try{await t.runExtract(e.cwd),e.parallelCompile?await t.runCompile(e.cwd,{parallel:!0}):await t.runCompile(e.cwd),console.log(`[fluenti] Extracting and compiling... done`),e.onSuccess?.();return}catch(t){let n=t instanceof Error?t:Error(String(t));if(e.throwOnError)throw n;console.warn(`[fluenti] Extract/compile failed:`,n.message),e.onError?.(n);return}let r=`[fluenti] @fluenti/cli is required for auto-compile.
178
+ `),s}function f(e){return e.replace(/\\/g,`\\\\`).replace(/'/g,`\\'`).replace(/\n/g,`\\n`).replace(/\r/g,`\\r`).replace(/\0/g,`\\0`)}function p(e){return e.split(`\\`).join(`/`)}async function m(e){if(e.compileOnly)try{let{runCompile:t}=(0,s.createRequire)((0,n.join)(e.cwd,`package.json`))(`@fluenti/cli`);await t(e.cwd),console.log(`[fluenti] Compiling... done`),e.onSuccess?.();return}catch(t){let n=t instanceof Error?t:Error(String(t));if(e.throwOnError)throw n;console.warn(`[fluenti] Compile failed:`,n.message),e.onError?.(n);return}let t=null;try{t=(0,s.createRequire)((0,n.join)(e.cwd,`package.json`))(`@fluenti/cli`)}catch{}if(t)try{await t.runExtract(e.cwd),e.parallelCompile?await t.runCompile(e.cwd,{parallel:!0}):await t.runCompile(e.cwd),console.log(`[fluenti] Extracting and compiling... done`),e.onSuccess?.();return}catch(t){let n=t instanceof Error?t:Error(String(t));if(e.throwOnError)throw n;console.warn(`[fluenti] Extract/compile failed:`,n.message),e.onError?.(n);return}let r=`[fluenti] @fluenti/cli is required for auto-compile.
176
179
  Install it as a devDependency:
177
180
  pnpm add -D @fluenti/cli
178
181
  See: https://fluenti.dev/start/introduction/`;if(e.throwOnError)throw Error(r);console.warn(r),e.onError?.(Error(r))}function h(e,t=300){let n=null,r=!1,i=!1;async function a(){r=!0;try{await m(e)}finally{r=!1,i&&(i=!1,o())}}function o(){n!==null&&clearTimeout(n),n=setTimeout(()=>{n=null,r?i=!0:a()},t)}return o}var g=null;function _(e,t){if(!t||t.length===0)return[(0,n.resolve)(e,`src`)];let r=new Set;for(let e of t){let t=e.replace(/^\.\//,``).split(`*`)[0].replace(/\/+$/,``);r.add(t||`.`)}return[...r].map(t=>(0,n.resolve)(e,t))}function v(t){g&&g();let{cwd:r,compiledDir:i,delay:a=1e3,include:o,exclude:c,parallelCompile:l}=t,u=(0,n.resolve)(r,i),d=(0,s.createRequire)(typeof __filename<`u`?__filename:{}.url)(`picomatch`),f=c?.length?d(c):()=>!1,p={cwd:r};l&&(p.parallelCompile=!0);let m=h(p,a);m();let v=_(r,o).map(t=>(0,e.watch)(t,{recursive:!0},(e,r)=>{r&&/\.[jt]sx?$/.test(r)&&(r.includes(`node_modules`)||r.includes(`.next`)||(0,n.resolve)(t,r).startsWith(u)||f(r)||m())})),y=()=>{for(let e of v)e.close();g=null};return g=y,y}function y(e){if(e&&!b(e))return x({},e);let t=e??{};return function(e){return x(t,e??{})}}function b(e){return[`config`,`serverModule`,`serverModuleOutDir`,`resolveLocale`,`cookieName`,`loaderEnforce`].some(t=>t in e)}function x(i,a){let o=process.cwd(),s=l(o,i),c=s.fluentiConfig,u=c.compileOutDir;(0,e.existsSync)((0,n.resolve)(o,u))||console.warn(`\n[fluenti] Compiled catalogs not found at ${u}.\nRun: npx fluenti extract && npx fluenti compile\n`);let f=d(o,s),p=(0,n.resolve)(typeof __dirname<`u`?__dirname:(0,n.dirname)((0,r.fileURLToPath)({}.url)),`loader.js`),m=a.webpack,h=!1,g=Object.fromEntries([`*.ts`,`*.tsx`,`*.js`,`*.jsx`].map(e=>[e,{condition:{not:`foreign`},loaders:[`@fluenti/next/loader`]}])),_=(0,n.resolve)((0,n.dirname)(f),`i18n-config.js`),y=`./`+(0,n.relative)(o,f).split(`\\`).join(`/`),b=`./`+(0,n.relative)(o,_).split(`\\`).join(`/`),x={"@fluenti/next":y,"@fluenti/next/i18n-config":b},C=process.env.NODE_ENV===`development`||process.argv.some(e=>e===`dev`),w=c.buildAutoCompile??!0;if(!C&&w&&!h){h=!0;try{(0,t.execSync)(`npx fluenti compile`+(c.parallelCompile?` --parallel`:``),{cwd:o,stdio:`inherit`})}catch{}}let T=c.devAutoCompile??!0;if(C&&T){let e={cwd:o,compiledDir:u,delay:c.devAutoCompileDelay??1e3};c.parallelCompile&&(e.parallelCompile=!0),c.include&&(e.include=c.include),c.exclude&&(e.exclude=c.exclude),v(e)}return{...a,turbopack:S(a.turbopack,{rules:g,resolveAlias:x}),webpack(e,t){let n=i.loaderEnforce===void 0&&!(`loaderEnforce`in i)?`pre`:i.loaderEnforce;return e.module.rules.push({test:/\.[jt]sx?$/,...n?{enforce:n}:{},exclude:[/node_modules/,/\.next/],use:[{loader:p,options:{serverModulePath:f}}]}),e.resolve=e.resolve??{},e.resolve.alias=e.resolve.alias??{},e.resolve.alias[`@fluenti/next$`]=f,e.resolve.alias[`@fluenti/next/i18n-config$`]=_,m?m(e,t):e}}}function S(e,t){let n=e??{},r=n.rules??{},i=Object.keys(t.rules).filter(e=>e in r);return i.length>0&&console.warn(`[fluenti] Your turbopack.rules override Fluenti's loader for: ${i.join(`, `)}.\n Fluenti's t\`\` transform will NOT run on these file types.\n If this is intentional, you can suppress this warning with { devAutoCompile: false }.`),{...n,rules:{...t.rules,...r},resolveAlias:{...t.resolveAlias,...n.resolveAlias}}}var C='[fluenti] `withFluenti()` must be configured in next.config.ts before importing from "@fluenti/next".';function w(){throw Error(C)}var T=w,E=w,D=w,O=w,k=w,A=w,j=w,M=w,N=w;exports.DateTime=j,exports.I18nProvider=N,exports.NumberFormat=M,exports.Plural=k,exports.Select=A,exports.Trans=O,exports.getI18n=E,Object.defineProperty(exports,`msg`,{enumerable:!0,get:function(){return c.msg}}),exports.setLocale=T,exports.t=D,exports.withFluenti=y;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/read-config.ts","../src/generate-server-module.ts","../src/dev-runner.ts","../src/dev-watcher.ts","../src/with-fluenti.ts","../src/index.ts"],"sourcesContent":["import { loadConfigSync, DEFAULT_FLUENTI_CONFIG } from '@fluenti/core/config'\nimport type { FluentiBuildConfig } from '@fluenti/core/internal'\nimport type { WithFluentConfig, ResolvedFluentConfig } from './types'\n\n/**\n * Read fluenti.config.ts and merge with withFluenti() overrides.\n *\n * Delegates config file loading to `@fluenti/core`'s shared `loadConfigSync()`.\n */\nexport function resolveConfig(\n projectRoot: string,\n overrides?: WithFluentConfig,\n): ResolvedFluentConfig {\n let fluentiConfig: FluentiBuildConfig\n\n if (overrides?.config && typeof overrides.config === 'object') {\n // Inline config — merge with defaults\n fluentiConfig = { ...DEFAULT_FLUENTI_CONFIG, ...overrides.config }\n } else {\n // string path or auto-discover\n fluentiConfig = loadConfigSync(\n typeof overrides?.config === 'string' ? overrides.config : undefined,\n projectRoot,\n )\n }\n\n const serverModuleOutDir = overrides?.serverModuleOutDir ?? '.fluenti'\n const cookieName = overrides?.cookieName ?? 'locale'\n\n const resolved: ResolvedFluentConfig = {\n fluentiConfig,\n serverModule: overrides?.serverModule ?? null,\n serverModuleOutDir,\n cookieName,\n }\n if (overrides?.resolveLocale) resolved.resolveLocale = overrides.resolveLocale\n return resolved\n}\n","import { writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, relative } from 'node:path'\nimport { validateLocale } from '@fluenti/core'\nimport { resolveLocaleCodes } from '@fluenti/core/internal'\nimport type { ResolvedFluentConfig } from './types'\n\n/** Wrapper around writeFileSync with a Fluenti-specific error message. */\nfunction writeSafe(path: string, content: string): void {\n try {\n writeFileSync(path, content, 'utf-8')\n } catch (err) {\n throw new Error(\n `[fluenti] Failed to write generated module at ${path}: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n}\n\n/**\n * Generate the server module that provides:\n * - setLocale / getI18n\n * - Trans / Plural / Select / DateTime / NumberFormat (server components)\n * - I18nProvider (async server component for layouts)\n *\n * @returns Absolute path to the generated server module.\n */\nexport function generateServerModule(\n projectRoot: string,\n config: ResolvedFluentConfig,\n): string {\n if (config.serverModule) {\n return resolve(projectRoot, config.serverModule)\n }\n\n const outDir = resolve(projectRoot, config.serverModuleOutDir)\n const outPath = resolve(outDir, 'server.js')\n const dtsPath = resolve(outDir, 'server.d.ts')\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true })\n }\n\n const locales = resolveLocaleCodes(config.fluentiConfig.locales)\n const defaultLocale = config.fluentiConfig.defaultLocale ?? config.fluentiConfig.sourceLocale\n const compiledDir = config.fluentiConfig.compileOutDir\n const fallbackChain = config.fluentiConfig.fallbackChain\n const dateFormats = config.fluentiConfig.dateFormats\n const numberFormats = config.fluentiConfig.numberFormats\n const cookieName = escapeJsSingleQuoted(config.cookieName)\n const defaultLocaleSafe = escapeJsSingleQuoted(defaultLocale)\n\n for (const locale of locales) {\n validateLocale(locale, 'next-plugin')\n }\n\n const compiledDirAbs = resolve(projectRoot, compiledDir)\n const compiledRelative = toForwardSlash(relative(outDir, compiledDirAbs))\n\n const localeImports = locales\n .map((locale) => ` case '${escapeJsSingleQuoted(locale)}': return import('${compiledRelative}/${locale}')`)\n .join('\\n')\n\n const fallbackChainStr = fallbackChain\n ? JSON.stringify(fallbackChain)\n : 'undefined'\n\n // Generate a 'use client' provider that imports messages statically.\n // Messages contain functions (interpolation) which can't cross the RSC boundary.\n const clientProviderPath = resolve(outDir, 'client-provider.js')\n\n const clientStaticImports = locales\n .map((locale) => {\n const safe = locale.replace(/[^a-zA-Z0-9]/g, '_')\n return `import ${safe} from '${compiledRelative}/${locale}'`\n })\n .join('\\n')\n\n const clientAllMessagesEntries = locales\n .map((locale) => {\n const safe = locale.replace(/[^a-zA-Z0-9]/g, '_')\n return `'${escapeJsSingleQuoted(locale)}': ${safe}`\n })\n .join(', ')\n\n const dateFormatsStr = dateFormats !== undefined ? JSON.stringify(dateFormats) : 'undefined'\n const numberFormatsStr = numberFormats !== undefined ? JSON.stringify(numberFormats) : 'undefined'\n\n const clientProviderSource = `\"use client\";\n// Auto-generated by @fluenti/next — do not edit\n// @ts-nocheck\nimport { createElement } from 'react'\nimport { I18nProvider } from '@fluenti/react'\n${clientStaticImports}\n\nconst __allMessages = { ${clientAllMessagesEntries} }\nconst __dateFormats = ${dateFormatsStr}\nconst __numberFormats = ${numberFormatsStr}\n\nexport function ClientI18nProvider({ locale, fallbackLocale, fallbackChain, children }) {\n return createElement(I18nProvider, { locale, fallbackLocale, messages: __allMessages, fallbackChain, dateFormats: __dateFormats, numberFormats: __numberFormats }, children)\n}\n`\n writeSafe(clientProviderPath, clientProviderSource)\n\n const clientProviderDtsPath = resolve(outDir, 'client-provider.d.ts')\n const clientProviderDtsSource = `// Auto-generated by @fluenti/next — do not edit\nimport type { ReactNode, ReactElement } from 'react'\n\nexport declare function ClientI18nProvider(props: {\n locale: string\n fallbackLocale: string\n fallbackChain?: Record<string, string[]>\n children: ReactNode\n}): ReactElement\n`\n writeSafe(clientProviderDtsPath, clientProviderDtsSource)\n\n const resolveLocaleImport = config.resolveLocale\n ? (() => {\n const absPath = resolve(projectRoot, config.resolveLocale)\n const relPath = toForwardSlash(relative(outDir, absPath))\n return `import __resolveLocale from '${relPath}'`\n })()\n : null\n\n const resolveLocaleFn = config.resolveLocale\n ? `resolveLocale: __resolveLocale,`\n : `resolveLocale: async () => {\n try {\n const { cookies, headers } = await import('next/headers')\n const [cookieStore, headerStore] = await Promise.all([cookies(), headers()])\n\n // 0. x-fluenti-locale header (set by createI18nMiddleware — most authoritative)\n const fromMiddleware = headerStore.get('x-fluenti-locale')\n if (fromMiddleware && __locales.includes(fromMiddleware)) return fromMiddleware\n\n // 1. Cookie (configurable name)\n const fromCookie = cookieStore.get('${cookieName}')?.value\n if (fromCookie && __locales.includes(fromCookie)) return fromCookie\n\n // 2. Accept-Language header\n const acceptLang = headerStore.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 return '${defaultLocaleSafe}'\n } catch {\n return '${defaultLocaleSafe}'\n }\n },`\n\n const localesArrayStr = JSON.stringify(locales)\n\n const moduleSource = `// Auto-generated by @fluenti/next — do not edit\n// @ts-nocheck\nimport { createServerI18n } from '@fluenti/react/server'\nimport { createElement } from 'react'\n${resolveLocaleImport ? `${resolveLocaleImport}\\n` : ''}\nconst __locales = ${localesArrayStr}\n\nconst serverI18n = createServerI18n({\n loadMessages: async (locale) => {\n switch (locale) {\n${localeImports}\n default: return import('${compiledRelative}/${defaultLocaleSafe}')\n }\n },\n fallbackLocale: '${defaultLocaleSafe}',\n fallbackChain: ${fallbackChainStr},\n ${resolveLocaleFn}\n})\n\nexport const setLocale = serverI18n.setLocale\nexport const getI18n = serverI18n.getI18n\nexport const t = (..._args) => {\n throw new Error(\n \"[fluenti] \\`t\\` imported from '@fluenti/next' is a compile-time API replaced at build time.\\\\n\" +\n ' Ensure:\\\\n' +\n ' 1. \\`withFluenti()\\` is configured in next.config.ts\\\\n' +\n ' 2. The file is inside src/ (not node_modules)\\\\n' +\n \" 3. For client components, import from '@fluenti/react'\",\n )\n}\nexport const Trans = serverI18n.Trans\nexport const Plural = serverI18n.Plural\nexport const Select = serverI18n.Select\nexport const DateTime = serverI18n.DateTime\nexport const NumberFormat = serverI18n.NumberFormat\n\n/**\n * Async server component for root layouts.\n *\n * Sets up both server-side (React.cache) and client-side (I18nProvider) i18n.\n */\nexport async function I18nProvider({ locale, children }) {\n const activeLocale = (locale && locale.trim()) ? locale : '${defaultLocaleSafe}'\n\n // 1. Initialize server-side i18n (React.cache scoped)\n serverI18n.setLocale(activeLocale)\n await serverI18n.getI18n()\n\n // 2. Import the local 'use client' provider that has messages statically bundled.\n // Messages contain functions (interpolation) which can't be serialized across the RSC boundary.\n const { ClientI18nProvider } = await import('./client-provider.js')\n\n return createElement(ClientI18nProvider, {\n locale: activeLocale,\n fallbackLocale: '${defaultLocaleSafe}',\n fallbackChain: ${fallbackChainStr},\n }, children)\n}\n`\n\n const dtsSource = `// Auto-generated by @fluenti/next — do not edit\nimport type { ReactNode, ReactElement } from 'react'\nimport type { CompileTimeT, FluentiCoreInstanceFull } from '@fluenti/core'\n\nexport declare function setLocale(locale: string): void\nexport declare function getI18n(): Promise<FluentiCoreInstanceFull & { locale: string }>\nexport declare const t: CompileTimeT\n\nexport declare function Trans(props: {\n children: ReactNode\n id?: string\n context?: string\n comment?: string\n render?: (translation: ReactNode) => ReactNode\n}): Promise<ReactElement>\n\nexport declare function Plural(props: {\n value: number\n id?: string\n context?: string\n comment?: string\n zero?: ReactNode\n one?: ReactNode\n two?: ReactNode\n few?: ReactNode\n many?: ReactNode\n other: ReactNode\n offset?: number\n}): Promise<ReactElement>\n\nexport declare function Select(props: {\n value: string\n id?: string\n context?: string\n comment?: string\n other: ReactNode\n options?: Record<string, ReactNode>\n [key: string]: ReactNode | Record<string, ReactNode> | string | undefined\n}): Promise<ReactElement>\n\nexport declare function DateTime(props: {\n value: Date | number\n format?: string\n}): Promise<ReactElement>\n\nexport declare function NumberFormat(props: {\n value: number\n format?: string\n}): Promise<ReactElement>\n\nexport declare function I18nProvider(props: {\n locale?: string\n children: ReactNode\n}): Promise<ReactElement>\n`\n\n writeSafe(outPath, moduleSource)\n writeSafe(dtsPath, dtsSource)\n\n // Generate Edge-safe i18n config for use in middleware\n const configModulePath = resolve(outDir, 'i18n-config.js')\n const configModuleDtsPath = resolve(outDir, 'i18n-config.d.ts')\n\n const configModuleSource = `// Auto-generated by @fluenti/next — do not edit\nexport const locales = ${localesArrayStr}\nexport const sourceLocale = '${defaultLocaleSafe}'\nexport const cookieName = '${cookieName}'\n`\n writeSafe(configModulePath, configModuleSource)\n\n const configModuleDtsSource = `// Auto-generated by @fluenti/next — do not edit\nexport declare const locales: string[]\nexport declare const sourceLocale: string\nexport declare const cookieName: string\n`\n writeSafe(configModuleDtsPath, configModuleDtsSource)\n\n return outPath\n}\n\n/** Escape a string for safe embedding inside a single-quoted JS string literal. */\nfunction escapeJsSingleQuoted(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")\n}\n\nfunction toForwardSlash(p: string): string {\n return p.split('\\\\').join('/')\n}\n","import { join } from 'node:path'\nimport { createRequire } from 'node:module'\n\nexport interface DevRunnerOptions {\n cwd: string\n onSuccess?: () => void\n onError?: (err: Error) => void\n /** If true, reject the promise on failure instead of swallowing the error */\n throwOnError?: boolean\n /** Run only compile (skip extract). Useful for production builds where source is unchanged. */\n compileOnly?: boolean\n /** Enable parallel compilation across locales using worker threads */\n parallelCompile?: boolean\n}\n\n/**\n * Run compile in-process via `@fluenti/cli` (for compileOnly mode),\n * or extract + compile in dev mode. Requires `@fluenti/cli` to be installed\n * as a devDependency.\n */\nexport async function runExtractCompile(options: DevRunnerOptions): Promise<void> {\n if (options.compileOnly) {\n try {\n // Resolve @fluenti/cli from the project's cwd (not from this package's location)\n // using createRequire so pnpm's strict node_modules layout works correctly.\n // Use require() (not import()) to load @fluenti/cli — avoids CJS/ESM interop\n // issues when dynamic import() loads minified CJS with chunk requires.\n const projectRequire = createRequire(join(options.cwd, 'package.json'))\n const { runCompile } = projectRequire('@fluenti/cli')\n await runCompile(options.cwd)\n console.log('[fluenti] Compiling... done')\n options.onSuccess?.()\n return\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e))\n if (options.throwOnError) throw error\n console.warn('[fluenti] Compile failed:', error.message)\n options.onError?.(error)\n return\n }\n }\n\n // Dev mode: run in-process extract + compile.\n // Step 1: load @fluenti/cli — if not installed, guide user to install it.\n // Step 2: run — errors here mean the CLI ran but failed; surface them.\n let fluentCli: { runExtract: (cwd: string) => Promise<void>; runCompile: (cwd: string, opts?: { parallel: boolean }) => Promise<void> } | null = null\n try {\n const projectRequire = createRequire(join(options.cwd, 'package.json'))\n fluentCli = projectRequire('@fluenti/cli')\n } catch {\n // @fluenti/cli not installed — will show install guide below\n }\n\n if (fluentCli) {\n try {\n await fluentCli.runExtract(options.cwd)\n if (options.parallelCompile) {\n await fluentCli.runCompile(options.cwd, { parallel: true })\n } else {\n await fluentCli.runCompile(options.cwd)\n }\n console.log('[fluenti] Extracting and compiling... done')\n options.onSuccess?.()\n return\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e))\n if (options.throwOnError) throw error\n console.warn('[fluenti] Extract/compile failed:', error.message)\n options.onError?.(error)\n return\n }\n }\n\n const msg =\n '[fluenti] @fluenti/cli is required for auto-compile.\\n' +\n ' Install it as a devDependency:\\n' +\n ' pnpm add -D @fluenti/cli\\n' +\n ' See: https://fluenti.dev/start/introduction/'\n if (options.throwOnError) {\n throw new Error(msg)\n }\n console.warn(msg)\n options.onError?.(new Error(msg))\n}\n\n/**\n * Create a debounced runner that collapses rapid calls.\n *\n * - If called while idle, schedules a run after `delay` ms.\n * - If called while a run is in progress, marks a pending rerun.\n * - Never runs concurrently.\n */\nexport function createDebouncedRunner(\n options: DevRunnerOptions,\n delay = 300,\n): () => void {\n let timer: ReturnType<typeof setTimeout> | null = null\n let running = false\n let pendingRerun = false\n\n async function execute(): Promise<void> {\n running = true\n try {\n await runExtractCompile(options)\n } finally {\n running = false\n if (pendingRerun) {\n pendingRerun = false\n schedule()\n }\n }\n }\n\n function schedule(): void {\n if (timer !== null) {\n clearTimeout(timer)\n }\n timer = setTimeout(() => {\n timer = null\n if (running) {\n pendingRerun = true\n } else {\n execute()\n }\n }, delay)\n }\n\n return schedule\n}\n","import { watch } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { createRequire } from 'node:module'\nimport { createDebouncedRunner } from './dev-runner'\n\nexport interface DevWatcherOptions {\n cwd: string\n compiledDir: string\n delay?: number\n /** Glob patterns from fluenti.config.ts `include` field */\n include?: string[]\n /** Glob patterns from fluenti.config.ts `exclude` field */\n exclude?: string[]\n /** Enable parallel compilation across locales using worker threads */\n parallelCompile?: boolean\n}\n\nlet activeWatcher: (() => void) | null = null\n\n/**\n * Extract watch directories from include glob patterns.\n *\n * Takes the static prefix before the first glob wildcard (`*`).\n * Falls back to `src` if no include patterns are provided.\n *\n * @example\n * extractWatchDirs('/proj', ['./src/**\\/*.tsx']) → ['/proj/src']\n * extractWatchDirs('/proj', ['./app/**\\/*.ts', './lib/**\\/*.ts']) → ['/proj/app', '/proj/lib']\n * extractWatchDirs('/proj', ['./**\\/*.ts']) → ['/proj']\n */\nexport function extractWatchDirs(cwd: string, include?: string[]): string[] {\n if (!include || include.length === 0) {\n return [resolve(cwd, 'src')]\n }\n\n const dirs = new Set<string>()\n for (const pattern of include) {\n const normalized = pattern.replace(/^\\.\\//, '')\n const staticPart = normalized.split('*')[0]!.replace(/\\/+$/, '')\n dirs.add(staticPart || '.')\n }\n return [...dirs].map(d => resolve(cwd, d))\n}\n\n/**\n * Start a standalone file watcher for dev auto-compile.\n *\n * Works independently of webpack/Turbopack — watches source directories\n * (inferred from `include` patterns) for changes and triggers\n * extract+compile via the debounced runner.\n *\n * Only one watcher is active at a time (guards against multiple `applyFluenti()` calls).\n *\n * @returns A cleanup function that stops the watcher.\n */\nexport function startDevWatcher(options: DevWatcherOptions): () => void {\n // Clean up previous watcher if one exists (e.g., dev server reload)\n if (activeWatcher) {\n activeWatcher()\n }\n\n const { cwd, compiledDir, delay = 1000, include, exclude, parallelCompile } = options\n const compiledDirResolved = resolve(cwd, compiledDir)\n const _require = createRequire(\n typeof __filename !== 'undefined' ? __filename : import.meta.url,\n )\n const picomatch = _require('picomatch') as (patterns: string[]) => (str: string) => boolean\n const isExcluded = exclude?.length ? picomatch(exclude) : () => false\n const runnerOpts: Parameters<typeof createDebouncedRunner>[0] = { cwd }\n if (parallelCompile) runnerOpts.parallelCompile = true\n const debouncedRun = createDebouncedRunner(runnerOpts, delay)\n\n // Initial run\n debouncedRun()\n\n const watchDirs = extractWatchDirs(cwd, include)\n const watchers = watchDirs.map(dir =>\n watch(dir, { recursive: true }, (_event, filename) => {\n if (!filename) return\n if (!/\\.[jt]sx?$/.test(filename)) return\n if (filename.includes('node_modules') || filename.includes('.next')) return\n const full = resolve(dir, filename)\n if (full.startsWith(compiledDirResolved)) return\n if (isExcluded(filename)) return\n debouncedRun()\n }),\n )\n\n const cleanup = (): void => {\n for (const w of watchers) w.close()\n activeWatcher = null\n }\n\n activeWatcher = cleanup\n return cleanup\n}\n","import { existsSync } from 'node:fs'\nimport { execSync } from 'node:child_process'\nimport { resolve, dirname, relative } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { WithFluentConfig } from './types'\nimport { resolveConfig } from './read-config'\nimport { generateServerModule } from './generate-server-module'\nimport { startDevWatcher } from './dev-watcher'\n\n// Use Record<string, any> to accept both Next 15's and 16's NextConfig types\n// (Next 16 removed the index signature from NextConfig)\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype NextConfig = Record<string, any>\n\n/**\n * Wrap your Next.js config with Fluenti support.\n *\n * Adds a webpack loader that transforms `t\\`\\`` and `t()` calls,\n * and generates a server module for RSC i18n.\n *\n * @example\n * ```ts\n * // next.config.ts — function style (recommended)\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti()({ reactStrictMode: true })\n * ```\n *\n * @example\n * ```ts\n * // next.config.ts — direct style\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti({ reactStrictMode: true })\n * ```\n */\nexport function withFluenti(fluentConfig?: WithFluentConfig): (nextConfig?: NextConfig) => NextConfig\nexport function withFluenti(nextConfig: NextConfig): NextConfig\nexport function withFluenti(\n configOrNext?: WithFluentConfig | NextConfig,\n): NextConfig | ((nextConfig?: NextConfig) => NextConfig) {\n if (configOrNext && !isFluentConfig(configOrNext as Record<string, unknown>)) {\n // Has keys but none are fluent-specific → treat as NextConfig\n return applyFluenti({}, configOrNext as NextConfig)\n }\n\n const fluentConfig = (configOrNext ?? {}) as WithFluentConfig\n return function wrappedConfig(nextConfig?: NextConfig): NextConfig {\n return applyFluenti(fluentConfig, nextConfig ?? {})\n }\n}\n\nfunction isFluentConfig(obj: Record<string, unknown>): boolean {\n const fluentOnlyKeys = [\n 'config', 'serverModule', 'serverModuleOutDir', 'resolveLocale',\n 'cookieName', 'loaderEnforce',\n ]\n return fluentOnlyKeys.some((key) => key in obj)\n}\n\nfunction applyFluenti(\n fluentConfig: WithFluentConfig,\n nextConfig: NextConfig,\n): NextConfig {\n const projectRoot = process.cwd()\n const resolved = resolveConfig(projectRoot, fluentConfig)\n const fluentiConfig = resolved.fluentiConfig\n const compiledDir = fluentiConfig.compileOutDir\n\n // Warn if compiled catalogs directory doesn't exist yet\n const compiledDirAbs = resolve(projectRoot, compiledDir)\n if (!existsSync(compiledDirAbs)) {\n console.warn(\n `\\n[fluenti] Compiled catalogs not found at ${compiledDir}.\\n` +\n `Run: npx fluenti extract && npx fluenti compile\\n`,\n )\n }\n\n // Generate server module for RSC\n const serverModulePath = generateServerModule(projectRoot, resolved)\n\n // Resolve the loader path — use import.meta.url for ESM compatibility\n const thisDir = typeof __dirname !== 'undefined'\n ? __dirname\n : dirname(fileURLToPath(import.meta.url))\n const loaderPath = resolve(thisDir, 'loader.js')\n\n const existingWebpack = nextConfig['webpack'] as\n | ((config: WebpackConfig, options: WebpackOptions) => WebpackConfig)\n | undefined\n\n let buildCompileRan = false\n\n // ── Turbopack config ──────────────────────────────────────────────\n // Turbopack loader-runner supports webpack loaders via turbopack.rules.\n // Use package name (not file path) — Turbopack resolves loaders as packages,\n // file paths trigger static analysis errors (TP1006) in the loader-runner.\n const fluentTurboRules = Object.fromEntries(\n ['*.ts', '*.tsx', '*.js', '*.jsx'].map((ext) => [\n ext,\n {\n condition: { not: 'foreign' },\n loaders: ['@fluenti/next/loader'],\n },\n ]),\n )\n\n // Turbopack resolveAlias requires relative paths (absolute paths get\n // misinterpreted as \"./abs/path\"). Use \"./\" + relative-from-cwd.\n const configModulePath = resolve(dirname(serverModulePath), 'i18n-config.js')\n const relativeServerModule = './' + relative(projectRoot, serverModulePath).split('\\\\').join('/')\n const relativeConfigModule = './' + relative(projectRoot, configModulePath).split('\\\\').join('/')\n const fluentTurboAlias: Record<string, string> = {\n '@fluenti/next': relativeServerModule,\n '@fluenti/next/i18n-config': relativeConfigModule,\n }\n\n // ── Build auto-compile (bundler-agnostic — runs at config time) ──\n const isDev = process.env['NODE_ENV'] === 'development'\n || process.argv.some(a => a === 'dev')\n const buildAutoCompile = fluentiConfig.buildAutoCompile ?? true\n\n if (!isDev && buildAutoCompile && !buildCompileRan) {\n buildCompileRan = true\n try {\n execSync(\n 'npx fluenti compile' + (fluentiConfig.parallelCompile ? ' --parallel' : ''),\n { cwd: projectRoot, stdio: 'inherit' },\n )\n } catch {\n // @fluenti/cli not installed or compile failed — user sees stdio output\n }\n }\n\n // ── Dev auto-compile via standalone watcher (works with both webpack & Turbopack) ──\n const devAutoCompile = fluentiConfig.devAutoCompile ?? true\n\n if (isDev && devAutoCompile) {\n const watcherOpts: Parameters<typeof startDevWatcher>[0] = {\n cwd: projectRoot,\n compiledDir,\n delay: fluentiConfig.devAutoCompileDelay ?? 1000,\n }\n if (fluentiConfig.parallelCompile) watcherOpts.parallelCompile = true\n if (fluentiConfig.include) watcherOpts.include = fluentiConfig.include\n if (fluentiConfig.exclude) watcherOpts.exclude = fluentiConfig.exclude\n startDevWatcher(watcherOpts)\n }\n\n return {\n ...nextConfig,\n turbopack: mergeTurbopackConfig(\n nextConfig['turbopack'] as Record<string, unknown> | undefined,\n { rules: fluentTurboRules, resolveAlias: fluentTurboAlias },\n ),\n webpack(config: WebpackConfig, options: WebpackOptions) {\n // Add fluenti loader\n const loaderEnforce = fluentConfig.loaderEnforce === undefined && !('loaderEnforce' in (fluentConfig as Record<string, unknown>))\n ? 'pre' as const\n : fluentConfig.loaderEnforce\n config.module.rules.push({\n test: /\\.[jt]sx?$/,\n ...(loaderEnforce ? { enforce: loaderEnforce } : {}),\n exclude: [/node_modules/, /\\.next/],\n use: [\n {\n loader: loaderPath,\n options: {\n serverModulePath,\n },\n },\n ],\n })\n\n // Add resolve alias so loader can import from generated server module\n config.resolve = config.resolve ?? {} as WebpackConfig['resolve']\n config.resolve.alias = config.resolve.alias ?? {}\n config.resolve.alias['@fluenti/next$'] = serverModulePath\n config.resolve.alias['@fluenti/next/i18n-config$'] = configModulePath\n\n // Call user's webpack config if provided\n if (existingWebpack) {\n return existingWebpack(config, options)\n }\n\n return config\n },\n }\n}\n\nfunction mergeTurbopackConfig(\n existing: Record<string, unknown> | undefined,\n fluenti: { rules: Record<string, unknown>; resolveAlias: Record<string, string> },\n): Record<string, unknown> {\n const base = existing ?? {}\n const userRules = (base['rules'] as Record<string, unknown>) ?? {}\n\n // Warn when user rules override fluenti's source-file rules\n const fluentKeys = Object.keys(fluenti.rules)\n const overlapping = fluentKeys.filter(k => k in userRules)\n if (overlapping.length > 0) {\n console.warn(\n `[fluenti] Your turbopack.rules override Fluenti's loader for: ${overlapping.join(', ')}.\\n` +\n ` Fluenti's t\\`\\` transform will NOT run on these file types.\\n` +\n ` If this is intentional, you can suppress this warning with { devAutoCompile: false }.`,\n )\n }\n\n return {\n ...base,\n rules: { ...fluenti.rules, ...userRules },\n resolveAlias: { ...fluenti.resolveAlias, ...(base['resolveAlias'] as Record<string, string>) },\n }\n}\n\n// Minimal webpack types for the config function\ninterface WebpackConfig {\n module: {\n rules: Array<Record<string, unknown>>\n }\n resolve: {\n alias?: Record<string, string>\n }\n}\n\ninterface WebpackOptions {\n isServer: boolean\n dev: boolean\n}\n","/**\n * @fluenti/next — Next.js plugin for Fluenti\n *\n * Provides:\n * - `withFluenti()` — wraps next.config.ts with t`` transform support\n * - I18nProvider — async server component (exported from generated module)\n * - Webpack loader for strict, binding-aware tagged-template optimization\n *\n * @example\n * ```ts\n * // next.config.ts\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti()({ reactStrictMode: true })\n * ```\n *\n * @example\n * ```tsx\n * // app/layout.tsx — resolved by webpack alias to the generated module\n * import { I18nProvider } from '@fluenti/next'\n * ```\n */\nexport { withFluenti } from './with-fluenti'\nexport type { WithFluentConfig, I18nProviderProps } from './types'\nexport { msg } from '@fluenti/react'\n\n// ── Runtime stubs ────────────────────────────────────────────────────\n// TypeScript resolves types from this file (via package.json exports).\n// At runtime, webpack `resolve.alias` redirects `@fluenti/next$` to the\n// generated server module, so these stubs are never actually called in\n// a correctly configured project. They exist only to provide helpful\n// errors if `withFluenti()` is not configured.\n\nimport type { ReactNode, ReactElement } from 'react'\nimport type { CompileTimeT, FluentiCoreInstanceFull } from '@fluenti/core'\n\nconst NOT_CONFIGURED =\n '[fluenti] `withFluenti()` must be configured in next.config.ts before importing from \"@fluenti/next\".'\n\nfunction throwNotConfigured(): never {\n throw new Error(NOT_CONFIGURED)\n}\n\n/** @see Generated module for the real implementation. */\nexport const setLocale: (locale: string) => void = throwNotConfigured\n/** @see Generated module for the real implementation. */\nexport const getI18n: () => Promise<FluentiCoreInstanceFull & { locale: string }> = throwNotConfigured as () => Promise<FluentiCoreInstanceFull & { locale: string }>\n/** @see Generated module for the real implementation. */\nexport const t: CompileTimeT = throwNotConfigured as unknown as CompileTimeT\n/** @see Generated module for the real implementation. */\nexport const Trans: (props: { children: ReactNode; id?: string; context?: string; comment?: string; render?: (translation: ReactNode) => ReactNode }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Trans\n/** @see Generated module for the real implementation. */\nexport const Plural: (props: { value: number; id?: string; context?: string; comment?: string; zero?: ReactNode; one?: ReactNode; two?: ReactNode; few?: ReactNode; many?: ReactNode; other: ReactNode; offset?: number }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Plural\n/** @see Generated module for the real implementation. */\nexport const Select: (props: { value: string; id?: string; context?: string; comment?: string; other: ReactNode; options?: Record<string, ReactNode>; [key: string]: ReactNode | Record<string, ReactNode> | string | undefined }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Select\n/** @see Generated module for the real implementation. */\nexport const DateTime: (props: { value: Date | number; style?: string }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof DateTime\n/** @see Generated module for the real implementation. */\nexport const NumberFormat: (props: { value: number; style?: string }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof NumberFormat\n/** @see Generated module for the real implementation. */\nexport const I18nProvider: (props: { locale?: string; children: ReactNode }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof I18nProvider\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/read-config.ts","../src/generate-server-module.ts","../src/dev-runner.ts","../src/dev-watcher.ts","../src/with-fluenti.ts","../src/index.ts"],"sourcesContent":["import { loadConfigSync, DEFAULT_FLUENTI_CONFIG } from '@fluenti/core/config'\nimport type { FluentiBuildConfig } from '@fluenti/core/internal'\nimport type { WithFluentConfig, ResolvedFluentConfig } from './types'\n\n/**\n * Read fluenti.config.ts and merge with withFluenti() overrides.\n *\n * Delegates config file loading to `@fluenti/core`'s shared `loadConfigSync()`.\n */\nexport function resolveConfig(\n projectRoot: string,\n overrides?: WithFluentConfig,\n): ResolvedFluentConfig {\n let fluentiConfig: FluentiBuildConfig\n\n if (overrides?.config && typeof overrides.config === 'object') {\n // Inline config — merge with defaults\n fluentiConfig = { ...DEFAULT_FLUENTI_CONFIG, ...overrides.config }\n } else {\n // string path or auto-discover\n fluentiConfig = loadConfigSync(\n typeof overrides?.config === 'string' ? overrides.config : undefined,\n projectRoot,\n )\n }\n\n const serverModuleOutDir = overrides?.serverModuleOutDir ?? '.fluenti'\n const cookieName = overrides?.cookieName ?? 'locale'\n\n const resolved: ResolvedFluentConfig = {\n fluentiConfig,\n serverModule: overrides?.serverModule ?? null,\n serverModuleOutDir,\n cookieName,\n }\n if (overrides?.resolveLocale) resolved.resolveLocale = overrides.resolveLocale\n return resolved\n}\n","import { writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, relative } from 'node:path'\nimport { validateLocale } from '@fluenti/core'\nimport { resolveLocaleCodes } from '@fluenti/core/internal'\nimport type { ResolvedFluentConfig } from './types'\n\n/** Wrapper around writeFileSync with a Fluenti-specific error message. */\nfunction writeSafe(path: string, content: string): void {\n try {\n writeFileSync(path, content, 'utf-8')\n } catch (err) {\n throw new Error(\n `[fluenti] Failed to write generated module at ${path}: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n}\n\n/**\n * Generate the server module that provides:\n * - setLocale / getI18n\n * - Trans / Plural / Select / DateTime / NumberFormat (server components)\n * - I18nProvider (async server component for layouts)\n *\n * @returns Absolute path to the generated server module.\n */\nexport function generateServerModule(\n projectRoot: string,\n config: ResolvedFluentConfig,\n): string {\n if (config.serverModule) {\n return resolve(projectRoot, config.serverModule)\n }\n\n const outDir = resolve(projectRoot, config.serverModuleOutDir)\n const outPath = resolve(outDir, 'server.js')\n const dtsPath = resolve(outDir, 'server.d.ts')\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true })\n }\n\n const locales = resolveLocaleCodes(config.fluentiConfig.locales)\n const defaultLocale = config.fluentiConfig.defaultLocale ?? config.fluentiConfig.sourceLocale\n const compiledDir = config.fluentiConfig.compileOutDir\n const fallbackChain = config.fluentiConfig.fallbackChain\n const dateFormats = config.fluentiConfig.dateFormats\n const numberFormats = config.fluentiConfig.numberFormats\n const cookieName = escapeJsSingleQuoted(config.cookieName)\n const defaultLocaleSafe = escapeJsSingleQuoted(defaultLocale)\n\n for (const locale of locales) {\n validateLocale(locale, 'next-plugin')\n }\n\n const compiledDirAbs = resolve(projectRoot, compiledDir)\n const compiledRelative = toForwardSlash(relative(outDir, compiledDirAbs))\n\n const localeImports = locales\n .map((locale) => ` case '${escapeJsSingleQuoted(locale)}': return import('${compiledRelative}/${locale}')`)\n .join('\\n')\n\n const fallbackChainStr = fallbackChain\n ? JSON.stringify(fallbackChain)\n : 'undefined'\n\n // Generate a 'use client' provider that imports messages statically.\n // Messages contain functions (interpolation) which can't cross the RSC boundary.\n const clientProviderPath = resolve(outDir, 'client-provider.js')\n\n const clientStaticImports = locales\n .map((locale) => {\n const safe = locale.replace(/[^a-zA-Z0-9]/g, '_')\n return `import ${safe} from '${compiledRelative}/${locale}'`\n })\n .join('\\n')\n\n const clientAllMessagesEntries = locales\n .map((locale) => {\n const safe = locale.replace(/[^a-zA-Z0-9]/g, '_')\n return `'${escapeJsSingleQuoted(locale)}': ${safe}`\n })\n .join(', ')\n\n const dateFormatsStr = dateFormats !== undefined ? JSON.stringify(dateFormats) : 'undefined'\n const numberFormatsStr = numberFormats !== undefined ? JSON.stringify(numberFormats) : 'undefined'\n\n const clientProviderSource = `\"use client\";\n// Auto-generated by @fluenti/next — do not edit\n// @ts-nocheck\nimport { createElement } from 'react'\nimport { I18nProvider } from '@fluenti/react'\nimport { interpolate as __interpolate } from '@fluenti/core/internal'\n${clientStaticImports}\n\nconst __allMessages = { ${clientAllMessagesEntries} }\nconst __dateFormats = ${dateFormatsStr}\nconst __numberFormats = ${numberFormatsStr}\n\nexport function ClientI18nProvider({ locale, fallbackLocale, fallbackChain, children }) {\n return createElement(I18nProvider, { locale, fallbackLocale, messages: __allMessages, fallbackChain, dateFormats: __dateFormats, numberFormats: __numberFormats, interpolate: __interpolate }, children)\n}\n`\n writeSafe(clientProviderPath, clientProviderSource)\n\n const clientProviderDtsPath = resolve(outDir, 'client-provider.d.ts')\n const clientProviderDtsSource = `// Auto-generated by @fluenti/next — do not edit\nimport type { ReactNode, ReactElement } from 'react'\n\nexport declare function ClientI18nProvider(props: {\n locale: string\n fallbackLocale: string\n fallbackChain?: Record<string, string[]>\n children: ReactNode\n}): ReactElement\n`\n writeSafe(clientProviderDtsPath, clientProviderDtsSource)\n\n const resolveLocaleImport = config.resolveLocale\n ? (() => {\n const absPath = resolve(projectRoot, config.resolveLocale)\n const relPath = toForwardSlash(relative(outDir, absPath))\n return `import __resolveLocale from '${relPath}'`\n })()\n : null\n\n const resolveLocaleFn = config.resolveLocale\n ? `resolveLocale: __resolveLocale,`\n : `resolveLocale: async () => {\n try {\n const { cookies, headers } = await import('next/headers')\n const [cookieStore, headerStore] = await Promise.all([cookies(), headers()])\n\n // 0. x-fluenti-locale header (set by createI18nMiddleware — most authoritative)\n const fromMiddleware = headerStore.get('x-fluenti-locale')\n if (fromMiddleware && __locales.includes(fromMiddleware)) return fromMiddleware\n\n // 1. Cookie (configurable name)\n const fromCookie = cookieStore.get('${cookieName}')?.value\n if (fromCookie && __locales.includes(fromCookie)) return fromCookie\n\n // 2. Accept-Language header\n const acceptLang = headerStore.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 return '${defaultLocaleSafe}'\n } catch {\n return '${defaultLocaleSafe}'\n }\n },`\n\n const localesArrayStr = JSON.stringify(locales)\n\n const moduleSource = `// Auto-generated by @fluenti/next — do not edit\n// @ts-nocheck\nimport { createServerI18n } from '@fluenti/react/server'\nimport { createElement } from 'react'\nimport { interpolate as __interpolate } from '@fluenti/core/internal'\n${resolveLocaleImport ? `${resolveLocaleImport}\\n` : ''}\nconst __locales = ${localesArrayStr}\n\nconst serverI18n = createServerI18n({\n interpolate: __interpolate,\n loadMessages: async (locale) => {\n switch (locale) {\n${localeImports}\n default: return import('${compiledRelative}/${defaultLocaleSafe}')\n }\n },\n fallbackLocale: '${defaultLocaleSafe}',\n fallbackChain: ${fallbackChainStr},\n ${resolveLocaleFn}\n})\n\nexport const setLocale = serverI18n.setLocale\nexport const getI18n = serverI18n.getI18n\nexport const t = (..._args) => {\n throw new Error(\n \"[fluenti] \\`t\\` imported from '@fluenti/next' is a compile-time API replaced at build time.\\\\n\" +\n ' Ensure:\\\\n' +\n ' 1. \\`withFluenti()\\` is configured in next.config.ts\\\\n' +\n ' 2. The file is inside src/ (not node_modules)\\\\n' +\n \" 3. For client components, import from '@fluenti/react'\",\n )\n}\nexport const Trans = serverI18n.Trans\nexport const Plural = serverI18n.Plural\nexport const Select = serverI18n.Select\nexport const DateTime = serverI18n.DateTime\nexport const NumberFormat = serverI18n.NumberFormat\n\n/**\n * Async server component for root layouts.\n *\n * Sets up both server-side (React.cache) and client-side (I18nProvider) i18n.\n */\nexport async function I18nProvider({ locale, children }) {\n const activeLocale = (locale && locale.trim()) ? locale : '${defaultLocaleSafe}'\n\n // 1. Initialize server-side i18n (React.cache scoped)\n serverI18n.setLocale(activeLocale)\n await serverI18n.getI18n()\n\n // 2. Import the local 'use client' provider that has messages statically bundled.\n // Messages contain functions (interpolation) which can't be serialized across the RSC boundary.\n const { ClientI18nProvider } = await import('./client-provider.js')\n\n return createElement(ClientI18nProvider, {\n locale: activeLocale,\n fallbackLocale: '${defaultLocaleSafe}',\n fallbackChain: ${fallbackChainStr},\n }, children)\n}\n`\n\n const dtsSource = `// Auto-generated by @fluenti/next — do not edit\nimport type { ReactNode, ReactElement } from 'react'\nimport type { CompileTimeT, FluentiCoreInstanceFull } from '@fluenti/core'\n\nexport declare function setLocale(locale: string): void\nexport declare function getI18n(): Promise<FluentiCoreInstanceFull & { locale: string }>\nexport declare const t: CompileTimeT\n\nexport declare function Trans(props: {\n children: ReactNode\n id?: string\n context?: string\n comment?: string\n render?: (translation: ReactNode) => ReactNode\n}): Promise<ReactElement>\n\nexport declare function Plural(props: {\n value: number\n id?: string\n context?: string\n comment?: string\n zero?: ReactNode\n one?: ReactNode\n two?: ReactNode\n few?: ReactNode\n many?: ReactNode\n other: ReactNode\n offset?: number\n}): Promise<ReactElement>\n\nexport declare function Select(props: {\n value: string\n id?: string\n context?: string\n comment?: string\n other: ReactNode\n options?: Record<string, ReactNode>\n [key: string]: ReactNode | Record<string, ReactNode> | string | undefined\n}): Promise<ReactElement>\n\nexport declare function DateTime(props: {\n value: Date | number\n format?: string\n}): Promise<ReactElement>\n\nexport declare function NumberFormat(props: {\n value: number\n format?: string\n}): Promise<ReactElement>\n\nexport declare function I18nProvider(props: {\n locale?: string\n children: ReactNode\n}): Promise<ReactElement>\n`\n\n writeSafe(outPath, moduleSource)\n writeSafe(dtsPath, dtsSource)\n\n // Generate Edge-safe i18n config for use in middleware\n const configModulePath = resolve(outDir, 'i18n-config.js')\n const configModuleDtsPath = resolve(outDir, 'i18n-config.d.ts')\n\n const configModuleSource = `// Auto-generated by @fluenti/next — do not edit\nexport const locales = ${localesArrayStr}\nexport const sourceLocale = '${defaultLocaleSafe}'\nexport const cookieName = '${cookieName}'\n`\n writeSafe(configModulePath, configModuleSource)\n\n const configModuleDtsSource = `// Auto-generated by @fluenti/next — do not edit\nexport declare const locales: string[]\nexport declare const sourceLocale: string\nexport declare const cookieName: string\n`\n writeSafe(configModuleDtsPath, configModuleDtsSource)\n\n return outPath\n}\n\n/** Escape a string for safe embedding inside a single-quoted JS string literal. */\nfunction escapeJsSingleQuoted(s: string): string {\n return s\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\0/g, '\\\\0')\n}\n\nfunction toForwardSlash(p: string): string {\n return p.split('\\\\').join('/')\n}\n","import { join } from 'node:path'\nimport { createRequire } from 'node:module'\n\nexport interface DevRunnerOptions {\n cwd: string\n onSuccess?: () => void\n onError?: (err: Error) => void\n /** If true, reject the promise on failure instead of swallowing the error */\n throwOnError?: boolean\n /** Run only compile (skip extract). Useful for production builds where source is unchanged. */\n compileOnly?: boolean\n /** Enable parallel compilation across locales using worker threads */\n parallelCompile?: boolean\n}\n\n/**\n * Run compile in-process via `@fluenti/cli` (for compileOnly mode),\n * or extract + compile in dev mode. Requires `@fluenti/cli` to be installed\n * as a devDependency.\n */\nexport async function runExtractCompile(options: DevRunnerOptions): Promise<void> {\n if (options.compileOnly) {\n try {\n // Resolve @fluenti/cli from the project's cwd (not from this package's location)\n // using createRequire so pnpm's strict node_modules layout works correctly.\n // Use require() (not import()) to load @fluenti/cli — avoids CJS/ESM interop\n // issues when dynamic import() loads minified CJS with chunk requires.\n const projectRequire = createRequire(join(options.cwd, 'package.json'))\n const { runCompile } = projectRequire('@fluenti/cli')\n await runCompile(options.cwd)\n console.log('[fluenti] Compiling... done')\n options.onSuccess?.()\n return\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e))\n if (options.throwOnError) throw error\n console.warn('[fluenti] Compile failed:', error.message)\n options.onError?.(error)\n return\n }\n }\n\n // Dev mode: run in-process extract + compile.\n // Step 1: load @fluenti/cli — if not installed, guide user to install it.\n // Step 2: run — errors here mean the CLI ran but failed; surface them.\n let fluentCli: { runExtract: (cwd: string) => Promise<void>; runCompile: (cwd: string, opts?: { parallel: boolean }) => Promise<void> } | null = null\n try {\n const projectRequire = createRequire(join(options.cwd, 'package.json'))\n fluentCli = projectRequire('@fluenti/cli')\n } catch {\n // @fluenti/cli not installed — will show install guide below\n }\n\n if (fluentCli) {\n try {\n await fluentCli.runExtract(options.cwd)\n if (options.parallelCompile) {\n await fluentCli.runCompile(options.cwd, { parallel: true })\n } else {\n await fluentCli.runCompile(options.cwd)\n }\n console.log('[fluenti] Extracting and compiling... done')\n options.onSuccess?.()\n return\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e))\n if (options.throwOnError) throw error\n console.warn('[fluenti] Extract/compile failed:', error.message)\n options.onError?.(error)\n return\n }\n }\n\n const msg =\n '[fluenti] @fluenti/cli is required for auto-compile.\\n' +\n ' Install it as a devDependency:\\n' +\n ' pnpm add -D @fluenti/cli\\n' +\n ' See: https://fluenti.dev/start/introduction/'\n if (options.throwOnError) {\n throw new Error(msg)\n }\n console.warn(msg)\n options.onError?.(new Error(msg))\n}\n\n/**\n * Create a debounced runner that collapses rapid calls.\n *\n * - If called while idle, schedules a run after `delay` ms.\n * - If called while a run is in progress, marks a pending rerun.\n * - Never runs concurrently.\n */\nexport function createDebouncedRunner(\n options: DevRunnerOptions,\n delay = 300,\n): () => void {\n let timer: ReturnType<typeof setTimeout> | null = null\n let running = false\n let pendingRerun = false\n\n async function execute(): Promise<void> {\n running = true\n try {\n await runExtractCompile(options)\n } finally {\n running = false\n if (pendingRerun) {\n pendingRerun = false\n schedule()\n }\n }\n }\n\n function schedule(): void {\n if (timer !== null) {\n clearTimeout(timer)\n }\n timer = setTimeout(() => {\n timer = null\n if (running) {\n pendingRerun = true\n } else {\n execute()\n }\n }, delay)\n }\n\n return schedule\n}\n","import { watch } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { createRequire } from 'node:module'\nimport { createDebouncedRunner } from './dev-runner'\n\nexport interface DevWatcherOptions {\n cwd: string\n compiledDir: string\n delay?: number\n /** Glob patterns from fluenti.config.ts `include` field */\n include?: string[]\n /** Glob patterns from fluenti.config.ts `exclude` field */\n exclude?: string[]\n /** Enable parallel compilation across locales using worker threads */\n parallelCompile?: boolean\n}\n\nlet activeWatcher: (() => void) | null = null\n\n/**\n * Extract watch directories from include glob patterns.\n *\n * Takes the static prefix before the first glob wildcard (`*`).\n * Falls back to `src` if no include patterns are provided.\n *\n * @example\n * extractWatchDirs('/proj', ['./src/**\\/*.tsx']) → ['/proj/src']\n * extractWatchDirs('/proj', ['./app/**\\/*.ts', './lib/**\\/*.ts']) → ['/proj/app', '/proj/lib']\n * extractWatchDirs('/proj', ['./**\\/*.ts']) → ['/proj']\n */\nexport function extractWatchDirs(cwd: string, include?: string[]): string[] {\n if (!include || include.length === 0) {\n return [resolve(cwd, 'src')]\n }\n\n const dirs = new Set<string>()\n for (const pattern of include) {\n const normalized = pattern.replace(/^\\.\\//, '')\n const staticPart = normalized.split('*')[0]!.replace(/\\/+$/, '')\n dirs.add(staticPart || '.')\n }\n return [...dirs].map(d => resolve(cwd, d))\n}\n\n/**\n * Start a standalone file watcher for dev auto-compile.\n *\n * Works independently of webpack/Turbopack — watches source directories\n * (inferred from `include` patterns) for changes and triggers\n * extract+compile via the debounced runner.\n *\n * Only one watcher is active at a time (guards against multiple `applyFluenti()` calls).\n *\n * @returns A cleanup function that stops the watcher.\n */\nexport function startDevWatcher(options: DevWatcherOptions): () => void {\n // Clean up previous watcher if one exists (e.g., dev server reload)\n if (activeWatcher) {\n activeWatcher()\n }\n\n const { cwd, compiledDir, delay = 1000, include, exclude, parallelCompile } = options\n const compiledDirResolved = resolve(cwd, compiledDir)\n const _require = createRequire(\n typeof __filename !== 'undefined' ? __filename : import.meta.url,\n )\n const picomatch = _require('picomatch') as (patterns: string[]) => (str: string) => boolean\n const isExcluded = exclude?.length ? picomatch(exclude) : () => false\n const runnerOpts: Parameters<typeof createDebouncedRunner>[0] = { cwd }\n if (parallelCompile) runnerOpts.parallelCompile = true\n const debouncedRun = createDebouncedRunner(runnerOpts, delay)\n\n // Initial run\n debouncedRun()\n\n const watchDirs = extractWatchDirs(cwd, include)\n const watchers = watchDirs.map(dir =>\n watch(dir, { recursive: true }, (_event, filename) => {\n if (!filename) return\n if (!/\\.[jt]sx?$/.test(filename)) return\n if (filename.includes('node_modules') || filename.includes('.next')) return\n const full = resolve(dir, filename)\n if (full.startsWith(compiledDirResolved)) return\n if (isExcluded(filename)) return\n debouncedRun()\n }),\n )\n\n const cleanup = (): void => {\n for (const w of watchers) w.close()\n activeWatcher = null\n }\n\n activeWatcher = cleanup\n return cleanup\n}\n","import { existsSync } from 'node:fs'\nimport { execSync } from 'node:child_process'\nimport { resolve, dirname, relative } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { WithFluentConfig } from './types'\nimport { resolveConfig } from './read-config'\nimport { generateServerModule } from './generate-server-module'\nimport { startDevWatcher } from './dev-watcher'\n\n// Use Record<string, any> to accept both Next 15's and 16's NextConfig types\n// (Next 16 removed the index signature from NextConfig)\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype NextConfig = Record<string, any>\n\n/**\n * Wrap your Next.js config with Fluenti support.\n *\n * Adds a webpack loader that transforms `t\\`\\`` and `t()` calls,\n * and generates a server module for RSC i18n.\n *\n * @example\n * ```ts\n * // next.config.ts — function style (recommended)\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti()({ reactStrictMode: true })\n * ```\n *\n * @example\n * ```ts\n * // next.config.ts — direct style\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti({ reactStrictMode: true })\n * ```\n */\nexport function withFluenti(fluentConfig?: WithFluentConfig): (nextConfig?: NextConfig) => NextConfig\nexport function withFluenti(nextConfig: NextConfig): NextConfig\nexport function withFluenti(\n configOrNext?: WithFluentConfig | NextConfig,\n): NextConfig | ((nextConfig?: NextConfig) => NextConfig) {\n if (configOrNext && !isFluentConfig(configOrNext as Record<string, unknown>)) {\n // Has keys but none are fluent-specific → treat as NextConfig\n return applyFluenti({}, configOrNext as NextConfig)\n }\n\n const fluentConfig = (configOrNext ?? {}) as WithFluentConfig\n return function wrappedConfig(nextConfig?: NextConfig): NextConfig {\n return applyFluenti(fluentConfig, nextConfig ?? {})\n }\n}\n\nfunction isFluentConfig(obj: Record<string, unknown>): boolean {\n const fluentOnlyKeys = [\n 'config', 'serverModule', 'serverModuleOutDir', 'resolveLocale',\n 'cookieName', 'loaderEnforce',\n ]\n return fluentOnlyKeys.some((key) => key in obj)\n}\n\nfunction applyFluenti(\n fluentConfig: WithFluentConfig,\n nextConfig: NextConfig,\n): NextConfig {\n const projectRoot = process.cwd()\n const resolved = resolveConfig(projectRoot, fluentConfig)\n const fluentiConfig = resolved.fluentiConfig\n const compiledDir = fluentiConfig.compileOutDir\n\n // Warn if compiled catalogs directory doesn't exist yet\n const compiledDirAbs = resolve(projectRoot, compiledDir)\n if (!existsSync(compiledDirAbs)) {\n console.warn(\n `\\n[fluenti] Compiled catalogs not found at ${compiledDir}.\\n` +\n `Run: npx fluenti extract && npx fluenti compile\\n`,\n )\n }\n\n // Generate server module for RSC\n const serverModulePath = generateServerModule(projectRoot, resolved)\n\n // Resolve the loader path — use import.meta.url for ESM compatibility\n const thisDir = typeof __dirname !== 'undefined'\n ? __dirname\n : dirname(fileURLToPath(import.meta.url))\n const loaderPath = resolve(thisDir, 'loader.js')\n\n const existingWebpack = nextConfig['webpack'] as\n | ((config: WebpackConfig, options: WebpackOptions) => WebpackConfig)\n | undefined\n\n let buildCompileRan = false\n\n // ── Turbopack config ──────────────────────────────────────────────\n // Turbopack loader-runner supports webpack loaders via turbopack.rules.\n // Use package name (not file path) — Turbopack resolves loaders as packages,\n // file paths trigger static analysis errors (TP1006) in the loader-runner.\n const fluentTurboRules = Object.fromEntries(\n ['*.ts', '*.tsx', '*.js', '*.jsx'].map((ext) => [\n ext,\n {\n condition: { not: 'foreign' },\n loaders: ['@fluenti/next/loader'],\n },\n ]),\n )\n\n // Turbopack resolveAlias requires relative paths (absolute paths get\n // misinterpreted as \"./abs/path\"). Use \"./\" + relative-from-cwd.\n const configModulePath = resolve(dirname(serverModulePath), 'i18n-config.js')\n const relativeServerModule = './' + relative(projectRoot, serverModulePath).split('\\\\').join('/')\n const relativeConfigModule = './' + relative(projectRoot, configModulePath).split('\\\\').join('/')\n const fluentTurboAlias: Record<string, string> = {\n '@fluenti/next': relativeServerModule,\n '@fluenti/next/i18n-config': relativeConfigModule,\n }\n\n // ── Build auto-compile (bundler-agnostic — runs at config time) ──\n const isDev = process.env['NODE_ENV'] === 'development'\n || process.argv.some(a => a === 'dev')\n const buildAutoCompile = fluentiConfig.buildAutoCompile ?? true\n\n if (!isDev && buildAutoCompile && !buildCompileRan) {\n buildCompileRan = true\n try {\n execSync(\n 'npx fluenti compile' + (fluentiConfig.parallelCompile ? ' --parallel' : ''),\n { cwd: projectRoot, stdio: 'inherit' },\n )\n } catch {\n // @fluenti/cli not installed or compile failed — user sees stdio output\n }\n }\n\n // ── Dev auto-compile via standalone watcher (works with both webpack & Turbopack) ──\n const devAutoCompile = fluentiConfig.devAutoCompile ?? true\n\n if (isDev && devAutoCompile) {\n const watcherOpts: Parameters<typeof startDevWatcher>[0] = {\n cwd: projectRoot,\n compiledDir,\n delay: fluentiConfig.devAutoCompileDelay ?? 1000,\n }\n if (fluentiConfig.parallelCompile) watcherOpts.parallelCompile = true\n if (fluentiConfig.include) watcherOpts.include = fluentiConfig.include\n if (fluentiConfig.exclude) watcherOpts.exclude = fluentiConfig.exclude\n startDevWatcher(watcherOpts)\n }\n\n return {\n ...nextConfig,\n turbopack: mergeTurbopackConfig(\n nextConfig['turbopack'] as Record<string, unknown> | undefined,\n { rules: fluentTurboRules, resolveAlias: fluentTurboAlias },\n ),\n webpack(config: WebpackConfig, options: WebpackOptions) {\n // Add fluenti loader\n const loaderEnforce = fluentConfig.loaderEnforce === undefined && !('loaderEnforce' in (fluentConfig as Record<string, unknown>))\n ? 'pre' as const\n : fluentConfig.loaderEnforce\n config.module.rules.push({\n test: /\\.[jt]sx?$/,\n ...(loaderEnforce ? { enforce: loaderEnforce } : {}),\n exclude: [/node_modules/, /\\.next/],\n use: [\n {\n loader: loaderPath,\n options: {\n serverModulePath,\n },\n },\n ],\n })\n\n // Add resolve alias so loader can import from generated server module\n config.resolve = config.resolve ?? {} as WebpackConfig['resolve']\n config.resolve.alias = config.resolve.alias ?? {}\n config.resolve.alias['@fluenti/next$'] = serverModulePath\n config.resolve.alias['@fluenti/next/i18n-config$'] = configModulePath\n\n // Call user's webpack config if provided\n if (existingWebpack) {\n return existingWebpack(config, options)\n }\n\n return config\n },\n }\n}\n\nfunction mergeTurbopackConfig(\n existing: Record<string, unknown> | undefined,\n fluenti: { rules: Record<string, unknown>; resolveAlias: Record<string, string> },\n): Record<string, unknown> {\n const base = existing ?? {}\n const userRules = (base['rules'] as Record<string, unknown>) ?? {}\n\n // Warn when user rules override fluenti's source-file rules\n const fluentKeys = Object.keys(fluenti.rules)\n const overlapping = fluentKeys.filter(k => k in userRules)\n if (overlapping.length > 0) {\n console.warn(\n `[fluenti] Your turbopack.rules override Fluenti's loader for: ${overlapping.join(', ')}.\\n` +\n ` Fluenti's t\\`\\` transform will NOT run on these file types.\\n` +\n ` If this is intentional, you can suppress this warning with { devAutoCompile: false }.`,\n )\n }\n\n return {\n ...base,\n rules: { ...fluenti.rules, ...userRules },\n resolveAlias: { ...fluenti.resolveAlias, ...(base['resolveAlias'] as Record<string, string>) },\n }\n}\n\n// Minimal webpack types for the config function\ninterface WebpackConfig {\n module: {\n rules: Array<Record<string, unknown>>\n }\n resolve: {\n alias?: Record<string, string>\n }\n}\n\ninterface WebpackOptions {\n isServer: boolean\n dev: boolean\n}\n","/**\n * @fluenti/next — Next.js plugin for Fluenti\n *\n * Provides:\n * - `withFluenti()` — wraps next.config.ts with t`` transform support\n * - I18nProvider — async server component (exported from generated module)\n * - Webpack loader for strict, binding-aware tagged-template optimization\n *\n * @example\n * ```ts\n * // next.config.ts\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti()({ reactStrictMode: true })\n * ```\n *\n * @example\n * ```tsx\n * // app/layout.tsx — resolved by webpack alias to the generated module\n * import { I18nProvider } from '@fluenti/next'\n * ```\n */\nexport { withFluenti } from './with-fluenti'\nexport type { WithFluentConfig, I18nProviderProps } from './types'\nexport { msg } from '@fluenti/react'\n\n// ── Runtime stubs ────────────────────────────────────────────────────\n// TypeScript resolves types from this file (via package.json exports).\n// At runtime, webpack `resolve.alias` redirects `@fluenti/next$` to the\n// generated server module, so these stubs are never actually called in\n// a correctly configured project. They exist only to provide helpful\n// errors if `withFluenti()` is not configured.\n\nimport type { ReactNode, ReactElement } from 'react'\nimport type { CompileTimeT, FluentiCoreInstanceFull } from '@fluenti/core'\n\nconst NOT_CONFIGURED =\n '[fluenti] `withFluenti()` must be configured in next.config.ts before importing from \"@fluenti/next\".'\n\nfunction throwNotConfigured(): never {\n throw new Error(NOT_CONFIGURED)\n}\n\n/** @see Generated module for the real implementation. */\nexport const setLocale: (locale: string) => void = throwNotConfigured\n/** @see Generated module for the real implementation. */\nexport const getI18n: () => Promise<FluentiCoreInstanceFull & { locale: string }> = throwNotConfigured as () => Promise<FluentiCoreInstanceFull & { locale: string }>\n/** @see Generated module for the real implementation. */\nexport const t: CompileTimeT = throwNotConfigured as unknown as CompileTimeT\n/** @see Generated module for the real implementation. */\nexport const Trans: (props: { children: ReactNode; id?: string; context?: string; comment?: string; render?: (translation: ReactNode) => ReactNode }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Trans\n/** @see Generated module for the real implementation. */\nexport const Plural: (props: { value: number; id?: string; context?: string; comment?: string; zero?: ReactNode; one?: ReactNode; two?: ReactNode; few?: ReactNode; many?: ReactNode; other: ReactNode; offset?: number }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Plural\n/** @see Generated module for the real implementation. */\nexport const Select: (props: { value: string; id?: string; context?: string; comment?: string; other: ReactNode; options?: Record<string, ReactNode>; [key: string]: ReactNode | Record<string, ReactNode> | string | undefined }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Select\n/** @see Generated module for the real implementation. */\nexport const DateTime: (props: { value: Date | number; style?: string }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof DateTime\n/** @see Generated module for the real implementation. */\nexport const NumberFormat: (props: { value: number; style?: string }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof NumberFormat\n/** @see Generated module for the real implementation. */\nexport const I18nProvider: (props: { locale?: string; children: ReactNode }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof I18nProvider\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -43,6 +43,7 @@ function v(n, r) {
43
43
  // @ts-nocheck
44
44
  import { createElement } from 'react'
45
45
  import { I18nProvider } from '@fluenti/react'
46
+ import { interpolate as __interpolate } from '@fluenti/core/internal'
46
47
  ${l.map((e) => `import ${e.replace(/[^a-zA-Z0-9]/g, "_")} from '${S}/${e}'`).join("\n")}
47
48
 
48
49
  const __allMessages = { ${l.map((e) => {
@@ -53,7 +54,7 @@ const __dateFormats = ${h === void 0 ? "undefined" : JSON.stringify(h)}
53
54
  const __numberFormats = ${g === void 0 ? "undefined" : JSON.stringify(g)}
54
55
 
55
56
  export function ClientI18nProvider({ locale, fallbackLocale, fallbackChain, children }) {
56
- return createElement(I18nProvider, { locale, fallbackLocale, messages: __allMessages, fallbackChain, dateFormats: __dateFormats, numberFormats: __numberFormats }, children)
57
+ return createElement(I18nProvider, { locale, fallbackLocale, messages: __allMessages, fallbackChain, dateFormats: __dateFormats, numberFormats: __numberFormats, interpolate: __interpolate }, children)
57
58
  }
58
59
  `), _(c(i, "client-provider.d.ts"), "// Auto-generated by @fluenti/next — do not edit\nimport type { ReactNode, ReactElement } from 'react'\n\nexport declare function ClientI18nProvider(props: {\n locale: string\n fallbackLocale: string\n fallbackChain?: Record<string, string[]>\n children: ReactNode\n}): ReactElement\n");
59
60
  let T = r.resolveLocale ? `import __resolveLocale from '${b(s(i, c(n, r.resolveLocale)))}'` : null, E = r.resolveLocale ? "resolveLocale: __resolveLocale," : `resolveLocale: async () => {
@@ -90,10 +91,12 @@ export function ClientI18nProvider({ locale, fallbackLocale, fallbackChain, chil
90
91
  // @ts-nocheck
91
92
  import { createServerI18n } from '@fluenti/react/server'
92
93
  import { createElement } from 'react'
94
+ import { interpolate as __interpolate } from '@fluenti/core/internal'
93
95
  ${T ? `${T}\n` : ""}
94
96
  const __locales = ${D}
95
97
 
96
98
  const serverI18n = createServerI18n({
99
+ interpolate: __interpolate,
97
100
  loadMessages: async (locale) => {
98
101
  switch (locale) {
99
102
  ${C}
@@ -153,7 +156,7 @@ export const cookieName = '${v}'
153
156
  `), _(k, "// Auto-generated by @fluenti/next — do not edit\nexport declare const locales: string[]\nexport declare const sourceLocale: string\nexport declare const cookieName: string\n"), a;
154
157
  }
155
158
  function y(e) {
156
- return e.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
159
+ return e.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\0/g, "\\0");
157
160
  }
158
161
  function b(e) {
159
162
  return e.split("\\").join("/");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/read-config.ts","../src/generate-server-module.ts","../src/dev-runner.ts","../src/dev-watcher.ts","../src/with-fluenti.ts","../src/index.ts"],"sourcesContent":["import { loadConfigSync, DEFAULT_FLUENTI_CONFIG } from '@fluenti/core/config'\nimport type { FluentiBuildConfig } from '@fluenti/core/internal'\nimport type { WithFluentConfig, ResolvedFluentConfig } from './types'\n\n/**\n * Read fluenti.config.ts and merge with withFluenti() overrides.\n *\n * Delegates config file loading to `@fluenti/core`'s shared `loadConfigSync()`.\n */\nexport function resolveConfig(\n projectRoot: string,\n overrides?: WithFluentConfig,\n): ResolvedFluentConfig {\n let fluentiConfig: FluentiBuildConfig\n\n if (overrides?.config && typeof overrides.config === 'object') {\n // Inline config — merge with defaults\n fluentiConfig = { ...DEFAULT_FLUENTI_CONFIG, ...overrides.config }\n } else {\n // string path or auto-discover\n fluentiConfig = loadConfigSync(\n typeof overrides?.config === 'string' ? overrides.config : undefined,\n projectRoot,\n )\n }\n\n const serverModuleOutDir = overrides?.serverModuleOutDir ?? '.fluenti'\n const cookieName = overrides?.cookieName ?? 'locale'\n\n const resolved: ResolvedFluentConfig = {\n fluentiConfig,\n serverModule: overrides?.serverModule ?? null,\n serverModuleOutDir,\n cookieName,\n }\n if (overrides?.resolveLocale) resolved.resolveLocale = overrides.resolveLocale\n return resolved\n}\n","import { writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, relative } from 'node:path'\nimport { validateLocale } from '@fluenti/core'\nimport { resolveLocaleCodes } from '@fluenti/core/internal'\nimport type { ResolvedFluentConfig } from './types'\n\n/** Wrapper around writeFileSync with a Fluenti-specific error message. */\nfunction writeSafe(path: string, content: string): void {\n try {\n writeFileSync(path, content, 'utf-8')\n } catch (err) {\n throw new Error(\n `[fluenti] Failed to write generated module at ${path}: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n}\n\n/**\n * Generate the server module that provides:\n * - setLocale / getI18n\n * - Trans / Plural / Select / DateTime / NumberFormat (server components)\n * - I18nProvider (async server component for layouts)\n *\n * @returns Absolute path to the generated server module.\n */\nexport function generateServerModule(\n projectRoot: string,\n config: ResolvedFluentConfig,\n): string {\n if (config.serverModule) {\n return resolve(projectRoot, config.serverModule)\n }\n\n const outDir = resolve(projectRoot, config.serverModuleOutDir)\n const outPath = resolve(outDir, 'server.js')\n const dtsPath = resolve(outDir, 'server.d.ts')\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true })\n }\n\n const locales = resolveLocaleCodes(config.fluentiConfig.locales)\n const defaultLocale = config.fluentiConfig.defaultLocale ?? config.fluentiConfig.sourceLocale\n const compiledDir = config.fluentiConfig.compileOutDir\n const fallbackChain = config.fluentiConfig.fallbackChain\n const dateFormats = config.fluentiConfig.dateFormats\n const numberFormats = config.fluentiConfig.numberFormats\n const cookieName = escapeJsSingleQuoted(config.cookieName)\n const defaultLocaleSafe = escapeJsSingleQuoted(defaultLocale)\n\n for (const locale of locales) {\n validateLocale(locale, 'next-plugin')\n }\n\n const compiledDirAbs = resolve(projectRoot, compiledDir)\n const compiledRelative = toForwardSlash(relative(outDir, compiledDirAbs))\n\n const localeImports = locales\n .map((locale) => ` case '${escapeJsSingleQuoted(locale)}': return import('${compiledRelative}/${locale}')`)\n .join('\\n')\n\n const fallbackChainStr = fallbackChain\n ? JSON.stringify(fallbackChain)\n : 'undefined'\n\n // Generate a 'use client' provider that imports messages statically.\n // Messages contain functions (interpolation) which can't cross the RSC boundary.\n const clientProviderPath = resolve(outDir, 'client-provider.js')\n\n const clientStaticImports = locales\n .map((locale) => {\n const safe = locale.replace(/[^a-zA-Z0-9]/g, '_')\n return `import ${safe} from '${compiledRelative}/${locale}'`\n })\n .join('\\n')\n\n const clientAllMessagesEntries = locales\n .map((locale) => {\n const safe = locale.replace(/[^a-zA-Z0-9]/g, '_')\n return `'${escapeJsSingleQuoted(locale)}': ${safe}`\n })\n .join(', ')\n\n const dateFormatsStr = dateFormats !== undefined ? JSON.stringify(dateFormats) : 'undefined'\n const numberFormatsStr = numberFormats !== undefined ? JSON.stringify(numberFormats) : 'undefined'\n\n const clientProviderSource = `\"use client\";\n// Auto-generated by @fluenti/next — do not edit\n// @ts-nocheck\nimport { createElement } from 'react'\nimport { I18nProvider } from '@fluenti/react'\n${clientStaticImports}\n\nconst __allMessages = { ${clientAllMessagesEntries} }\nconst __dateFormats = ${dateFormatsStr}\nconst __numberFormats = ${numberFormatsStr}\n\nexport function ClientI18nProvider({ locale, fallbackLocale, fallbackChain, children }) {\n return createElement(I18nProvider, { locale, fallbackLocale, messages: __allMessages, fallbackChain, dateFormats: __dateFormats, numberFormats: __numberFormats }, children)\n}\n`\n writeSafe(clientProviderPath, clientProviderSource)\n\n const clientProviderDtsPath = resolve(outDir, 'client-provider.d.ts')\n const clientProviderDtsSource = `// Auto-generated by @fluenti/next — do not edit\nimport type { ReactNode, ReactElement } from 'react'\n\nexport declare function ClientI18nProvider(props: {\n locale: string\n fallbackLocale: string\n fallbackChain?: Record<string, string[]>\n children: ReactNode\n}): ReactElement\n`\n writeSafe(clientProviderDtsPath, clientProviderDtsSource)\n\n const resolveLocaleImport = config.resolveLocale\n ? (() => {\n const absPath = resolve(projectRoot, config.resolveLocale)\n const relPath = toForwardSlash(relative(outDir, absPath))\n return `import __resolveLocale from '${relPath}'`\n })()\n : null\n\n const resolveLocaleFn = config.resolveLocale\n ? `resolveLocale: __resolveLocale,`\n : `resolveLocale: async () => {\n try {\n const { cookies, headers } = await import('next/headers')\n const [cookieStore, headerStore] = await Promise.all([cookies(), headers()])\n\n // 0. x-fluenti-locale header (set by createI18nMiddleware — most authoritative)\n const fromMiddleware = headerStore.get('x-fluenti-locale')\n if (fromMiddleware && __locales.includes(fromMiddleware)) return fromMiddleware\n\n // 1. Cookie (configurable name)\n const fromCookie = cookieStore.get('${cookieName}')?.value\n if (fromCookie && __locales.includes(fromCookie)) return fromCookie\n\n // 2. Accept-Language header\n const acceptLang = headerStore.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 return '${defaultLocaleSafe}'\n } catch {\n return '${defaultLocaleSafe}'\n }\n },`\n\n const localesArrayStr = JSON.stringify(locales)\n\n const moduleSource = `// Auto-generated by @fluenti/next — do not edit\n// @ts-nocheck\nimport { createServerI18n } from '@fluenti/react/server'\nimport { createElement } from 'react'\n${resolveLocaleImport ? `${resolveLocaleImport}\\n` : ''}\nconst __locales = ${localesArrayStr}\n\nconst serverI18n = createServerI18n({\n loadMessages: async (locale) => {\n switch (locale) {\n${localeImports}\n default: return import('${compiledRelative}/${defaultLocaleSafe}')\n }\n },\n fallbackLocale: '${defaultLocaleSafe}',\n fallbackChain: ${fallbackChainStr},\n ${resolveLocaleFn}\n})\n\nexport const setLocale = serverI18n.setLocale\nexport const getI18n = serverI18n.getI18n\nexport const t = (..._args) => {\n throw new Error(\n \"[fluenti] \\`t\\` imported from '@fluenti/next' is a compile-time API replaced at build time.\\\\n\" +\n ' Ensure:\\\\n' +\n ' 1. \\`withFluenti()\\` is configured in next.config.ts\\\\n' +\n ' 2. The file is inside src/ (not node_modules)\\\\n' +\n \" 3. For client components, import from '@fluenti/react'\",\n )\n}\nexport const Trans = serverI18n.Trans\nexport const Plural = serverI18n.Plural\nexport const Select = serverI18n.Select\nexport const DateTime = serverI18n.DateTime\nexport const NumberFormat = serverI18n.NumberFormat\n\n/**\n * Async server component for root layouts.\n *\n * Sets up both server-side (React.cache) and client-side (I18nProvider) i18n.\n */\nexport async function I18nProvider({ locale, children }) {\n const activeLocale = (locale && locale.trim()) ? locale : '${defaultLocaleSafe}'\n\n // 1. Initialize server-side i18n (React.cache scoped)\n serverI18n.setLocale(activeLocale)\n await serverI18n.getI18n()\n\n // 2. Import the local 'use client' provider that has messages statically bundled.\n // Messages contain functions (interpolation) which can't be serialized across the RSC boundary.\n const { ClientI18nProvider } = await import('./client-provider.js')\n\n return createElement(ClientI18nProvider, {\n locale: activeLocale,\n fallbackLocale: '${defaultLocaleSafe}',\n fallbackChain: ${fallbackChainStr},\n }, children)\n}\n`\n\n const dtsSource = `// Auto-generated by @fluenti/next — do not edit\nimport type { ReactNode, ReactElement } from 'react'\nimport type { CompileTimeT, FluentiCoreInstanceFull } from '@fluenti/core'\n\nexport declare function setLocale(locale: string): void\nexport declare function getI18n(): Promise<FluentiCoreInstanceFull & { locale: string }>\nexport declare const t: CompileTimeT\n\nexport declare function Trans(props: {\n children: ReactNode\n id?: string\n context?: string\n comment?: string\n render?: (translation: ReactNode) => ReactNode\n}): Promise<ReactElement>\n\nexport declare function Plural(props: {\n value: number\n id?: string\n context?: string\n comment?: string\n zero?: ReactNode\n one?: ReactNode\n two?: ReactNode\n few?: ReactNode\n many?: ReactNode\n other: ReactNode\n offset?: number\n}): Promise<ReactElement>\n\nexport declare function Select(props: {\n value: string\n id?: string\n context?: string\n comment?: string\n other: ReactNode\n options?: Record<string, ReactNode>\n [key: string]: ReactNode | Record<string, ReactNode> | string | undefined\n}): Promise<ReactElement>\n\nexport declare function DateTime(props: {\n value: Date | number\n format?: string\n}): Promise<ReactElement>\n\nexport declare function NumberFormat(props: {\n value: number\n format?: string\n}): Promise<ReactElement>\n\nexport declare function I18nProvider(props: {\n locale?: string\n children: ReactNode\n}): Promise<ReactElement>\n`\n\n writeSafe(outPath, moduleSource)\n writeSafe(dtsPath, dtsSource)\n\n // Generate Edge-safe i18n config for use in middleware\n const configModulePath = resolve(outDir, 'i18n-config.js')\n const configModuleDtsPath = resolve(outDir, 'i18n-config.d.ts')\n\n const configModuleSource = `// Auto-generated by @fluenti/next — do not edit\nexport const locales = ${localesArrayStr}\nexport const sourceLocale = '${defaultLocaleSafe}'\nexport const cookieName = '${cookieName}'\n`\n writeSafe(configModulePath, configModuleSource)\n\n const configModuleDtsSource = `// Auto-generated by @fluenti/next — do not edit\nexport declare const locales: string[]\nexport declare const sourceLocale: string\nexport declare const cookieName: string\n`\n writeSafe(configModuleDtsPath, configModuleDtsSource)\n\n return outPath\n}\n\n/** Escape a string for safe embedding inside a single-quoted JS string literal. */\nfunction escapeJsSingleQuoted(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")\n}\n\nfunction toForwardSlash(p: string): string {\n return p.split('\\\\').join('/')\n}\n","import { join } from 'node:path'\nimport { createRequire } from 'node:module'\n\nexport interface DevRunnerOptions {\n cwd: string\n onSuccess?: () => void\n onError?: (err: Error) => void\n /** If true, reject the promise on failure instead of swallowing the error */\n throwOnError?: boolean\n /** Run only compile (skip extract). Useful for production builds where source is unchanged. */\n compileOnly?: boolean\n /** Enable parallel compilation across locales using worker threads */\n parallelCompile?: boolean\n}\n\n/**\n * Run compile in-process via `@fluenti/cli` (for compileOnly mode),\n * or extract + compile in dev mode. Requires `@fluenti/cli` to be installed\n * as a devDependency.\n */\nexport async function runExtractCompile(options: DevRunnerOptions): Promise<void> {\n if (options.compileOnly) {\n try {\n // Resolve @fluenti/cli from the project's cwd (not from this package's location)\n // using createRequire so pnpm's strict node_modules layout works correctly.\n // Use require() (not import()) to load @fluenti/cli — avoids CJS/ESM interop\n // issues when dynamic import() loads minified CJS with chunk requires.\n const projectRequire = createRequire(join(options.cwd, 'package.json'))\n const { runCompile } = projectRequire('@fluenti/cli')\n await runCompile(options.cwd)\n console.log('[fluenti] Compiling... done')\n options.onSuccess?.()\n return\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e))\n if (options.throwOnError) throw error\n console.warn('[fluenti] Compile failed:', error.message)\n options.onError?.(error)\n return\n }\n }\n\n // Dev mode: run in-process extract + compile.\n // Step 1: load @fluenti/cli — if not installed, guide user to install it.\n // Step 2: run — errors here mean the CLI ran but failed; surface them.\n let fluentCli: { runExtract: (cwd: string) => Promise<void>; runCompile: (cwd: string, opts?: { parallel: boolean }) => Promise<void> } | null = null\n try {\n const projectRequire = createRequire(join(options.cwd, 'package.json'))\n fluentCli = projectRequire('@fluenti/cli')\n } catch {\n // @fluenti/cli not installed — will show install guide below\n }\n\n if (fluentCli) {\n try {\n await fluentCli.runExtract(options.cwd)\n if (options.parallelCompile) {\n await fluentCli.runCompile(options.cwd, { parallel: true })\n } else {\n await fluentCli.runCompile(options.cwd)\n }\n console.log('[fluenti] Extracting and compiling... done')\n options.onSuccess?.()\n return\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e))\n if (options.throwOnError) throw error\n console.warn('[fluenti] Extract/compile failed:', error.message)\n options.onError?.(error)\n return\n }\n }\n\n const msg =\n '[fluenti] @fluenti/cli is required for auto-compile.\\n' +\n ' Install it as a devDependency:\\n' +\n ' pnpm add -D @fluenti/cli\\n' +\n ' See: https://fluenti.dev/start/introduction/'\n if (options.throwOnError) {\n throw new Error(msg)\n }\n console.warn(msg)\n options.onError?.(new Error(msg))\n}\n\n/**\n * Create a debounced runner that collapses rapid calls.\n *\n * - If called while idle, schedules a run after `delay` ms.\n * - If called while a run is in progress, marks a pending rerun.\n * - Never runs concurrently.\n */\nexport function createDebouncedRunner(\n options: DevRunnerOptions,\n delay = 300,\n): () => void {\n let timer: ReturnType<typeof setTimeout> | null = null\n let running = false\n let pendingRerun = false\n\n async function execute(): Promise<void> {\n running = true\n try {\n await runExtractCompile(options)\n } finally {\n running = false\n if (pendingRerun) {\n pendingRerun = false\n schedule()\n }\n }\n }\n\n function schedule(): void {\n if (timer !== null) {\n clearTimeout(timer)\n }\n timer = setTimeout(() => {\n timer = null\n if (running) {\n pendingRerun = true\n } else {\n execute()\n }\n }, delay)\n }\n\n return schedule\n}\n","import { watch } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { createRequire } from 'node:module'\nimport { createDebouncedRunner } from './dev-runner'\n\nexport interface DevWatcherOptions {\n cwd: string\n compiledDir: string\n delay?: number\n /** Glob patterns from fluenti.config.ts `include` field */\n include?: string[]\n /** Glob patterns from fluenti.config.ts `exclude` field */\n exclude?: string[]\n /** Enable parallel compilation across locales using worker threads */\n parallelCompile?: boolean\n}\n\nlet activeWatcher: (() => void) | null = null\n\n/**\n * Extract watch directories from include glob patterns.\n *\n * Takes the static prefix before the first glob wildcard (`*`).\n * Falls back to `src` if no include patterns are provided.\n *\n * @example\n * extractWatchDirs('/proj', ['./src/**\\/*.tsx']) → ['/proj/src']\n * extractWatchDirs('/proj', ['./app/**\\/*.ts', './lib/**\\/*.ts']) → ['/proj/app', '/proj/lib']\n * extractWatchDirs('/proj', ['./**\\/*.ts']) → ['/proj']\n */\nexport function extractWatchDirs(cwd: string, include?: string[]): string[] {\n if (!include || include.length === 0) {\n return [resolve(cwd, 'src')]\n }\n\n const dirs = new Set<string>()\n for (const pattern of include) {\n const normalized = pattern.replace(/^\\.\\//, '')\n const staticPart = normalized.split('*')[0]!.replace(/\\/+$/, '')\n dirs.add(staticPart || '.')\n }\n return [...dirs].map(d => resolve(cwd, d))\n}\n\n/**\n * Start a standalone file watcher for dev auto-compile.\n *\n * Works independently of webpack/Turbopack — watches source directories\n * (inferred from `include` patterns) for changes and triggers\n * extract+compile via the debounced runner.\n *\n * Only one watcher is active at a time (guards against multiple `applyFluenti()` calls).\n *\n * @returns A cleanup function that stops the watcher.\n */\nexport function startDevWatcher(options: DevWatcherOptions): () => void {\n // Clean up previous watcher if one exists (e.g., dev server reload)\n if (activeWatcher) {\n activeWatcher()\n }\n\n const { cwd, compiledDir, delay = 1000, include, exclude, parallelCompile } = options\n const compiledDirResolved = resolve(cwd, compiledDir)\n const _require = createRequire(\n typeof __filename !== 'undefined' ? __filename : import.meta.url,\n )\n const picomatch = _require('picomatch') as (patterns: string[]) => (str: string) => boolean\n const isExcluded = exclude?.length ? picomatch(exclude) : () => false\n const runnerOpts: Parameters<typeof createDebouncedRunner>[0] = { cwd }\n if (parallelCompile) runnerOpts.parallelCompile = true\n const debouncedRun = createDebouncedRunner(runnerOpts, delay)\n\n // Initial run\n debouncedRun()\n\n const watchDirs = extractWatchDirs(cwd, include)\n const watchers = watchDirs.map(dir =>\n watch(dir, { recursive: true }, (_event, filename) => {\n if (!filename) return\n if (!/\\.[jt]sx?$/.test(filename)) return\n if (filename.includes('node_modules') || filename.includes('.next')) return\n const full = resolve(dir, filename)\n if (full.startsWith(compiledDirResolved)) return\n if (isExcluded(filename)) return\n debouncedRun()\n }),\n )\n\n const cleanup = (): void => {\n for (const w of watchers) w.close()\n activeWatcher = null\n }\n\n activeWatcher = cleanup\n return cleanup\n}\n","import { existsSync } from 'node:fs'\nimport { execSync } from 'node:child_process'\nimport { resolve, dirname, relative } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { WithFluentConfig } from './types'\nimport { resolveConfig } from './read-config'\nimport { generateServerModule } from './generate-server-module'\nimport { startDevWatcher } from './dev-watcher'\n\n// Use Record<string, any> to accept both Next 15's and 16's NextConfig types\n// (Next 16 removed the index signature from NextConfig)\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype NextConfig = Record<string, any>\n\n/**\n * Wrap your Next.js config with Fluenti support.\n *\n * Adds a webpack loader that transforms `t\\`\\`` and `t()` calls,\n * and generates a server module for RSC i18n.\n *\n * @example\n * ```ts\n * // next.config.ts — function style (recommended)\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti()({ reactStrictMode: true })\n * ```\n *\n * @example\n * ```ts\n * // next.config.ts — direct style\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti({ reactStrictMode: true })\n * ```\n */\nexport function withFluenti(fluentConfig?: WithFluentConfig): (nextConfig?: NextConfig) => NextConfig\nexport function withFluenti(nextConfig: NextConfig): NextConfig\nexport function withFluenti(\n configOrNext?: WithFluentConfig | NextConfig,\n): NextConfig | ((nextConfig?: NextConfig) => NextConfig) {\n if (configOrNext && !isFluentConfig(configOrNext as Record<string, unknown>)) {\n // Has keys but none are fluent-specific → treat as NextConfig\n return applyFluenti({}, configOrNext as NextConfig)\n }\n\n const fluentConfig = (configOrNext ?? {}) as WithFluentConfig\n return function wrappedConfig(nextConfig?: NextConfig): NextConfig {\n return applyFluenti(fluentConfig, nextConfig ?? {})\n }\n}\n\nfunction isFluentConfig(obj: Record<string, unknown>): boolean {\n const fluentOnlyKeys = [\n 'config', 'serverModule', 'serverModuleOutDir', 'resolveLocale',\n 'cookieName', 'loaderEnforce',\n ]\n return fluentOnlyKeys.some((key) => key in obj)\n}\n\nfunction applyFluenti(\n fluentConfig: WithFluentConfig,\n nextConfig: NextConfig,\n): NextConfig {\n const projectRoot = process.cwd()\n const resolved = resolveConfig(projectRoot, fluentConfig)\n const fluentiConfig = resolved.fluentiConfig\n const compiledDir = fluentiConfig.compileOutDir\n\n // Warn if compiled catalogs directory doesn't exist yet\n const compiledDirAbs = resolve(projectRoot, compiledDir)\n if (!existsSync(compiledDirAbs)) {\n console.warn(\n `\\n[fluenti] Compiled catalogs not found at ${compiledDir}.\\n` +\n `Run: npx fluenti extract && npx fluenti compile\\n`,\n )\n }\n\n // Generate server module for RSC\n const serverModulePath = generateServerModule(projectRoot, resolved)\n\n // Resolve the loader path — use import.meta.url for ESM compatibility\n const thisDir = typeof __dirname !== 'undefined'\n ? __dirname\n : dirname(fileURLToPath(import.meta.url))\n const loaderPath = resolve(thisDir, 'loader.js')\n\n const existingWebpack = nextConfig['webpack'] as\n | ((config: WebpackConfig, options: WebpackOptions) => WebpackConfig)\n | undefined\n\n let buildCompileRan = false\n\n // ── Turbopack config ──────────────────────────────────────────────\n // Turbopack loader-runner supports webpack loaders via turbopack.rules.\n // Use package name (not file path) — Turbopack resolves loaders as packages,\n // file paths trigger static analysis errors (TP1006) in the loader-runner.\n const fluentTurboRules = Object.fromEntries(\n ['*.ts', '*.tsx', '*.js', '*.jsx'].map((ext) => [\n ext,\n {\n condition: { not: 'foreign' },\n loaders: ['@fluenti/next/loader'],\n },\n ]),\n )\n\n // Turbopack resolveAlias requires relative paths (absolute paths get\n // misinterpreted as \"./abs/path\"). Use \"./\" + relative-from-cwd.\n const configModulePath = resolve(dirname(serverModulePath), 'i18n-config.js')\n const relativeServerModule = './' + relative(projectRoot, serverModulePath).split('\\\\').join('/')\n const relativeConfigModule = './' + relative(projectRoot, configModulePath).split('\\\\').join('/')\n const fluentTurboAlias: Record<string, string> = {\n '@fluenti/next': relativeServerModule,\n '@fluenti/next/i18n-config': relativeConfigModule,\n }\n\n // ── Build auto-compile (bundler-agnostic — runs at config time) ──\n const isDev = process.env['NODE_ENV'] === 'development'\n || process.argv.some(a => a === 'dev')\n const buildAutoCompile = fluentiConfig.buildAutoCompile ?? true\n\n if (!isDev && buildAutoCompile && !buildCompileRan) {\n buildCompileRan = true\n try {\n execSync(\n 'npx fluenti compile' + (fluentiConfig.parallelCompile ? ' --parallel' : ''),\n { cwd: projectRoot, stdio: 'inherit' },\n )\n } catch {\n // @fluenti/cli not installed or compile failed — user sees stdio output\n }\n }\n\n // ── Dev auto-compile via standalone watcher (works with both webpack & Turbopack) ──\n const devAutoCompile = fluentiConfig.devAutoCompile ?? true\n\n if (isDev && devAutoCompile) {\n const watcherOpts: Parameters<typeof startDevWatcher>[0] = {\n cwd: projectRoot,\n compiledDir,\n delay: fluentiConfig.devAutoCompileDelay ?? 1000,\n }\n if (fluentiConfig.parallelCompile) watcherOpts.parallelCompile = true\n if (fluentiConfig.include) watcherOpts.include = fluentiConfig.include\n if (fluentiConfig.exclude) watcherOpts.exclude = fluentiConfig.exclude\n startDevWatcher(watcherOpts)\n }\n\n return {\n ...nextConfig,\n turbopack: mergeTurbopackConfig(\n nextConfig['turbopack'] as Record<string, unknown> | undefined,\n { rules: fluentTurboRules, resolveAlias: fluentTurboAlias },\n ),\n webpack(config: WebpackConfig, options: WebpackOptions) {\n // Add fluenti loader\n const loaderEnforce = fluentConfig.loaderEnforce === undefined && !('loaderEnforce' in (fluentConfig as Record<string, unknown>))\n ? 'pre' as const\n : fluentConfig.loaderEnforce\n config.module.rules.push({\n test: /\\.[jt]sx?$/,\n ...(loaderEnforce ? { enforce: loaderEnforce } : {}),\n exclude: [/node_modules/, /\\.next/],\n use: [\n {\n loader: loaderPath,\n options: {\n serverModulePath,\n },\n },\n ],\n })\n\n // Add resolve alias so loader can import from generated server module\n config.resolve = config.resolve ?? {} as WebpackConfig['resolve']\n config.resolve.alias = config.resolve.alias ?? {}\n config.resolve.alias['@fluenti/next$'] = serverModulePath\n config.resolve.alias['@fluenti/next/i18n-config$'] = configModulePath\n\n // Call user's webpack config if provided\n if (existingWebpack) {\n return existingWebpack(config, options)\n }\n\n return config\n },\n }\n}\n\nfunction mergeTurbopackConfig(\n existing: Record<string, unknown> | undefined,\n fluenti: { rules: Record<string, unknown>; resolveAlias: Record<string, string> },\n): Record<string, unknown> {\n const base = existing ?? {}\n const userRules = (base['rules'] as Record<string, unknown>) ?? {}\n\n // Warn when user rules override fluenti's source-file rules\n const fluentKeys = Object.keys(fluenti.rules)\n const overlapping = fluentKeys.filter(k => k in userRules)\n if (overlapping.length > 0) {\n console.warn(\n `[fluenti] Your turbopack.rules override Fluenti's loader for: ${overlapping.join(', ')}.\\n` +\n ` Fluenti's t\\`\\` transform will NOT run on these file types.\\n` +\n ` If this is intentional, you can suppress this warning with { devAutoCompile: false }.`,\n )\n }\n\n return {\n ...base,\n rules: { ...fluenti.rules, ...userRules },\n resolveAlias: { ...fluenti.resolveAlias, ...(base['resolveAlias'] as Record<string, string>) },\n }\n}\n\n// Minimal webpack types for the config function\ninterface WebpackConfig {\n module: {\n rules: Array<Record<string, unknown>>\n }\n resolve: {\n alias?: Record<string, string>\n }\n}\n\ninterface WebpackOptions {\n isServer: boolean\n dev: boolean\n}\n","/**\n * @fluenti/next — Next.js plugin for Fluenti\n *\n * Provides:\n * - `withFluenti()` — wraps next.config.ts with t`` transform support\n * - I18nProvider — async server component (exported from generated module)\n * - Webpack loader for strict, binding-aware tagged-template optimization\n *\n * @example\n * ```ts\n * // next.config.ts\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti()({ reactStrictMode: true })\n * ```\n *\n * @example\n * ```tsx\n * // app/layout.tsx — resolved by webpack alias to the generated module\n * import { I18nProvider } from '@fluenti/next'\n * ```\n */\nexport { withFluenti } from './with-fluenti'\nexport type { WithFluentConfig, I18nProviderProps } from './types'\nexport { msg } from '@fluenti/react'\n\n// ── Runtime stubs ────────────────────────────────────────────────────\n// TypeScript resolves types from this file (via package.json exports).\n// At runtime, webpack `resolve.alias` redirects `@fluenti/next$` to the\n// generated server module, so these stubs are never actually called in\n// a correctly configured project. They exist only to provide helpful\n// errors if `withFluenti()` is not configured.\n\nimport type { ReactNode, ReactElement } from 'react'\nimport type { CompileTimeT, FluentiCoreInstanceFull } from '@fluenti/core'\n\nconst NOT_CONFIGURED =\n '[fluenti] `withFluenti()` must be configured in next.config.ts before importing from \"@fluenti/next\".'\n\nfunction throwNotConfigured(): never {\n throw new Error(NOT_CONFIGURED)\n}\n\n/** @see Generated module for the real implementation. */\nexport const setLocale: (locale: string) => void = throwNotConfigured\n/** @see Generated module for the real implementation. */\nexport const getI18n: () => Promise<FluentiCoreInstanceFull & { locale: string }> = throwNotConfigured as () => Promise<FluentiCoreInstanceFull & { locale: string }>\n/** @see Generated module for the real implementation. */\nexport const t: CompileTimeT = throwNotConfigured as unknown as CompileTimeT\n/** @see Generated module for the real implementation. */\nexport const Trans: (props: { children: ReactNode; id?: string; context?: string; comment?: string; render?: (translation: ReactNode) => ReactNode }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Trans\n/** @see Generated module for the real implementation. */\nexport const Plural: (props: { value: number; id?: string; context?: string; comment?: string; zero?: ReactNode; one?: ReactNode; two?: ReactNode; few?: ReactNode; many?: ReactNode; other: ReactNode; offset?: number }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Plural\n/** @see Generated module for the real implementation. */\nexport const Select: (props: { value: string; id?: string; context?: string; comment?: string; other: ReactNode; options?: Record<string, ReactNode>; [key: string]: ReactNode | Record<string, ReactNode> | string | undefined }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Select\n/** @see Generated module for the real implementation. */\nexport const DateTime: (props: { value: Date | number; style?: string }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof DateTime\n/** @see Generated module for the real implementation. */\nexport const NumberFormat: (props: { value: number; style?: string }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof NumberFormat\n/** @see Generated module for the real implementation. */\nexport const I18nProvider: (props: { locale?: string; children: ReactNode }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof I18nProvider\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/read-config.ts","../src/generate-server-module.ts","../src/dev-runner.ts","../src/dev-watcher.ts","../src/with-fluenti.ts","../src/index.ts"],"sourcesContent":["import { loadConfigSync, DEFAULT_FLUENTI_CONFIG } from '@fluenti/core/config'\nimport type { FluentiBuildConfig } from '@fluenti/core/internal'\nimport type { WithFluentConfig, ResolvedFluentConfig } from './types'\n\n/**\n * Read fluenti.config.ts and merge with withFluenti() overrides.\n *\n * Delegates config file loading to `@fluenti/core`'s shared `loadConfigSync()`.\n */\nexport function resolveConfig(\n projectRoot: string,\n overrides?: WithFluentConfig,\n): ResolvedFluentConfig {\n let fluentiConfig: FluentiBuildConfig\n\n if (overrides?.config && typeof overrides.config === 'object') {\n // Inline config — merge with defaults\n fluentiConfig = { ...DEFAULT_FLUENTI_CONFIG, ...overrides.config }\n } else {\n // string path or auto-discover\n fluentiConfig = loadConfigSync(\n typeof overrides?.config === 'string' ? overrides.config : undefined,\n projectRoot,\n )\n }\n\n const serverModuleOutDir = overrides?.serverModuleOutDir ?? '.fluenti'\n const cookieName = overrides?.cookieName ?? 'locale'\n\n const resolved: ResolvedFluentConfig = {\n fluentiConfig,\n serverModule: overrides?.serverModule ?? null,\n serverModuleOutDir,\n cookieName,\n }\n if (overrides?.resolveLocale) resolved.resolveLocale = overrides.resolveLocale\n return resolved\n}\n","import { writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, relative } from 'node:path'\nimport { validateLocale } from '@fluenti/core'\nimport { resolveLocaleCodes } from '@fluenti/core/internal'\nimport type { ResolvedFluentConfig } from './types'\n\n/** Wrapper around writeFileSync with a Fluenti-specific error message. */\nfunction writeSafe(path: string, content: string): void {\n try {\n writeFileSync(path, content, 'utf-8')\n } catch (err) {\n throw new Error(\n `[fluenti] Failed to write generated module at ${path}: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n}\n\n/**\n * Generate the server module that provides:\n * - setLocale / getI18n\n * - Trans / Plural / Select / DateTime / NumberFormat (server components)\n * - I18nProvider (async server component for layouts)\n *\n * @returns Absolute path to the generated server module.\n */\nexport function generateServerModule(\n projectRoot: string,\n config: ResolvedFluentConfig,\n): string {\n if (config.serverModule) {\n return resolve(projectRoot, config.serverModule)\n }\n\n const outDir = resolve(projectRoot, config.serverModuleOutDir)\n const outPath = resolve(outDir, 'server.js')\n const dtsPath = resolve(outDir, 'server.d.ts')\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true })\n }\n\n const locales = resolveLocaleCodes(config.fluentiConfig.locales)\n const defaultLocale = config.fluentiConfig.defaultLocale ?? config.fluentiConfig.sourceLocale\n const compiledDir = config.fluentiConfig.compileOutDir\n const fallbackChain = config.fluentiConfig.fallbackChain\n const dateFormats = config.fluentiConfig.dateFormats\n const numberFormats = config.fluentiConfig.numberFormats\n const cookieName = escapeJsSingleQuoted(config.cookieName)\n const defaultLocaleSafe = escapeJsSingleQuoted(defaultLocale)\n\n for (const locale of locales) {\n validateLocale(locale, 'next-plugin')\n }\n\n const compiledDirAbs = resolve(projectRoot, compiledDir)\n const compiledRelative = toForwardSlash(relative(outDir, compiledDirAbs))\n\n const localeImports = locales\n .map((locale) => ` case '${escapeJsSingleQuoted(locale)}': return import('${compiledRelative}/${locale}')`)\n .join('\\n')\n\n const fallbackChainStr = fallbackChain\n ? JSON.stringify(fallbackChain)\n : 'undefined'\n\n // Generate a 'use client' provider that imports messages statically.\n // Messages contain functions (interpolation) which can't cross the RSC boundary.\n const clientProviderPath = resolve(outDir, 'client-provider.js')\n\n const clientStaticImports = locales\n .map((locale) => {\n const safe = locale.replace(/[^a-zA-Z0-9]/g, '_')\n return `import ${safe} from '${compiledRelative}/${locale}'`\n })\n .join('\\n')\n\n const clientAllMessagesEntries = locales\n .map((locale) => {\n const safe = locale.replace(/[^a-zA-Z0-9]/g, '_')\n return `'${escapeJsSingleQuoted(locale)}': ${safe}`\n })\n .join(', ')\n\n const dateFormatsStr = dateFormats !== undefined ? JSON.stringify(dateFormats) : 'undefined'\n const numberFormatsStr = numberFormats !== undefined ? JSON.stringify(numberFormats) : 'undefined'\n\n const clientProviderSource = `\"use client\";\n// Auto-generated by @fluenti/next — do not edit\n// @ts-nocheck\nimport { createElement } from 'react'\nimport { I18nProvider } from '@fluenti/react'\nimport { interpolate as __interpolate } from '@fluenti/core/internal'\n${clientStaticImports}\n\nconst __allMessages = { ${clientAllMessagesEntries} }\nconst __dateFormats = ${dateFormatsStr}\nconst __numberFormats = ${numberFormatsStr}\n\nexport function ClientI18nProvider({ locale, fallbackLocale, fallbackChain, children }) {\n return createElement(I18nProvider, { locale, fallbackLocale, messages: __allMessages, fallbackChain, dateFormats: __dateFormats, numberFormats: __numberFormats, interpolate: __interpolate }, children)\n}\n`\n writeSafe(clientProviderPath, clientProviderSource)\n\n const clientProviderDtsPath = resolve(outDir, 'client-provider.d.ts')\n const clientProviderDtsSource = `// Auto-generated by @fluenti/next — do not edit\nimport type { ReactNode, ReactElement } from 'react'\n\nexport declare function ClientI18nProvider(props: {\n locale: string\n fallbackLocale: string\n fallbackChain?: Record<string, string[]>\n children: ReactNode\n}): ReactElement\n`\n writeSafe(clientProviderDtsPath, clientProviderDtsSource)\n\n const resolveLocaleImport = config.resolveLocale\n ? (() => {\n const absPath = resolve(projectRoot, config.resolveLocale)\n const relPath = toForwardSlash(relative(outDir, absPath))\n return `import __resolveLocale from '${relPath}'`\n })()\n : null\n\n const resolveLocaleFn = config.resolveLocale\n ? `resolveLocale: __resolveLocale,`\n : `resolveLocale: async () => {\n try {\n const { cookies, headers } = await import('next/headers')\n const [cookieStore, headerStore] = await Promise.all([cookies(), headers()])\n\n // 0. x-fluenti-locale header (set by createI18nMiddleware — most authoritative)\n const fromMiddleware = headerStore.get('x-fluenti-locale')\n if (fromMiddleware && __locales.includes(fromMiddleware)) return fromMiddleware\n\n // 1. Cookie (configurable name)\n const fromCookie = cookieStore.get('${cookieName}')?.value\n if (fromCookie && __locales.includes(fromCookie)) return fromCookie\n\n // 2. Accept-Language header\n const acceptLang = headerStore.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 return '${defaultLocaleSafe}'\n } catch {\n return '${defaultLocaleSafe}'\n }\n },`\n\n const localesArrayStr = JSON.stringify(locales)\n\n const moduleSource = `// Auto-generated by @fluenti/next — do not edit\n// @ts-nocheck\nimport { createServerI18n } from '@fluenti/react/server'\nimport { createElement } from 'react'\nimport { interpolate as __interpolate } from '@fluenti/core/internal'\n${resolveLocaleImport ? `${resolveLocaleImport}\\n` : ''}\nconst __locales = ${localesArrayStr}\n\nconst serverI18n = createServerI18n({\n interpolate: __interpolate,\n loadMessages: async (locale) => {\n switch (locale) {\n${localeImports}\n default: return import('${compiledRelative}/${defaultLocaleSafe}')\n }\n },\n fallbackLocale: '${defaultLocaleSafe}',\n fallbackChain: ${fallbackChainStr},\n ${resolveLocaleFn}\n})\n\nexport const setLocale = serverI18n.setLocale\nexport const getI18n = serverI18n.getI18n\nexport const t = (..._args) => {\n throw new Error(\n \"[fluenti] \\`t\\` imported from '@fluenti/next' is a compile-time API replaced at build time.\\\\n\" +\n ' Ensure:\\\\n' +\n ' 1. \\`withFluenti()\\` is configured in next.config.ts\\\\n' +\n ' 2. The file is inside src/ (not node_modules)\\\\n' +\n \" 3. For client components, import from '@fluenti/react'\",\n )\n}\nexport const Trans = serverI18n.Trans\nexport const Plural = serverI18n.Plural\nexport const Select = serverI18n.Select\nexport const DateTime = serverI18n.DateTime\nexport const NumberFormat = serverI18n.NumberFormat\n\n/**\n * Async server component for root layouts.\n *\n * Sets up both server-side (React.cache) and client-side (I18nProvider) i18n.\n */\nexport async function I18nProvider({ locale, children }) {\n const activeLocale = (locale && locale.trim()) ? locale : '${defaultLocaleSafe}'\n\n // 1. Initialize server-side i18n (React.cache scoped)\n serverI18n.setLocale(activeLocale)\n await serverI18n.getI18n()\n\n // 2. Import the local 'use client' provider that has messages statically bundled.\n // Messages contain functions (interpolation) which can't be serialized across the RSC boundary.\n const { ClientI18nProvider } = await import('./client-provider.js')\n\n return createElement(ClientI18nProvider, {\n locale: activeLocale,\n fallbackLocale: '${defaultLocaleSafe}',\n fallbackChain: ${fallbackChainStr},\n }, children)\n}\n`\n\n const dtsSource = `// Auto-generated by @fluenti/next — do not edit\nimport type { ReactNode, ReactElement } from 'react'\nimport type { CompileTimeT, FluentiCoreInstanceFull } from '@fluenti/core'\n\nexport declare function setLocale(locale: string): void\nexport declare function getI18n(): Promise<FluentiCoreInstanceFull & { locale: string }>\nexport declare const t: CompileTimeT\n\nexport declare function Trans(props: {\n children: ReactNode\n id?: string\n context?: string\n comment?: string\n render?: (translation: ReactNode) => ReactNode\n}): Promise<ReactElement>\n\nexport declare function Plural(props: {\n value: number\n id?: string\n context?: string\n comment?: string\n zero?: ReactNode\n one?: ReactNode\n two?: ReactNode\n few?: ReactNode\n many?: ReactNode\n other: ReactNode\n offset?: number\n}): Promise<ReactElement>\n\nexport declare function Select(props: {\n value: string\n id?: string\n context?: string\n comment?: string\n other: ReactNode\n options?: Record<string, ReactNode>\n [key: string]: ReactNode | Record<string, ReactNode> | string | undefined\n}): Promise<ReactElement>\n\nexport declare function DateTime(props: {\n value: Date | number\n format?: string\n}): Promise<ReactElement>\n\nexport declare function NumberFormat(props: {\n value: number\n format?: string\n}): Promise<ReactElement>\n\nexport declare function I18nProvider(props: {\n locale?: string\n children: ReactNode\n}): Promise<ReactElement>\n`\n\n writeSafe(outPath, moduleSource)\n writeSafe(dtsPath, dtsSource)\n\n // Generate Edge-safe i18n config for use in middleware\n const configModulePath = resolve(outDir, 'i18n-config.js')\n const configModuleDtsPath = resolve(outDir, 'i18n-config.d.ts')\n\n const configModuleSource = `// Auto-generated by @fluenti/next — do not edit\nexport const locales = ${localesArrayStr}\nexport const sourceLocale = '${defaultLocaleSafe}'\nexport const cookieName = '${cookieName}'\n`\n writeSafe(configModulePath, configModuleSource)\n\n const configModuleDtsSource = `// Auto-generated by @fluenti/next — do not edit\nexport declare const locales: string[]\nexport declare const sourceLocale: string\nexport declare const cookieName: string\n`\n writeSafe(configModuleDtsPath, configModuleDtsSource)\n\n return outPath\n}\n\n/** Escape a string for safe embedding inside a single-quoted JS string literal. */\nfunction escapeJsSingleQuoted(s: string): string {\n return s\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/'/g, \"\\\\'\")\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\0/g, '\\\\0')\n}\n\nfunction toForwardSlash(p: string): string {\n return p.split('\\\\').join('/')\n}\n","import { join } from 'node:path'\nimport { createRequire } from 'node:module'\n\nexport interface DevRunnerOptions {\n cwd: string\n onSuccess?: () => void\n onError?: (err: Error) => void\n /** If true, reject the promise on failure instead of swallowing the error */\n throwOnError?: boolean\n /** Run only compile (skip extract). Useful for production builds where source is unchanged. */\n compileOnly?: boolean\n /** Enable parallel compilation across locales using worker threads */\n parallelCompile?: boolean\n}\n\n/**\n * Run compile in-process via `@fluenti/cli` (for compileOnly mode),\n * or extract + compile in dev mode. Requires `@fluenti/cli` to be installed\n * as a devDependency.\n */\nexport async function runExtractCompile(options: DevRunnerOptions): Promise<void> {\n if (options.compileOnly) {\n try {\n // Resolve @fluenti/cli from the project's cwd (not from this package's location)\n // using createRequire so pnpm's strict node_modules layout works correctly.\n // Use require() (not import()) to load @fluenti/cli — avoids CJS/ESM interop\n // issues when dynamic import() loads minified CJS with chunk requires.\n const projectRequire = createRequire(join(options.cwd, 'package.json'))\n const { runCompile } = projectRequire('@fluenti/cli')\n await runCompile(options.cwd)\n console.log('[fluenti] Compiling... done')\n options.onSuccess?.()\n return\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e))\n if (options.throwOnError) throw error\n console.warn('[fluenti] Compile failed:', error.message)\n options.onError?.(error)\n return\n }\n }\n\n // Dev mode: run in-process extract + compile.\n // Step 1: load @fluenti/cli — if not installed, guide user to install it.\n // Step 2: run — errors here mean the CLI ran but failed; surface them.\n let fluentCli: { runExtract: (cwd: string) => Promise<void>; runCompile: (cwd: string, opts?: { parallel: boolean }) => Promise<void> } | null = null\n try {\n const projectRequire = createRequire(join(options.cwd, 'package.json'))\n fluentCli = projectRequire('@fluenti/cli')\n } catch {\n // @fluenti/cli not installed — will show install guide below\n }\n\n if (fluentCli) {\n try {\n await fluentCli.runExtract(options.cwd)\n if (options.parallelCompile) {\n await fluentCli.runCompile(options.cwd, { parallel: true })\n } else {\n await fluentCli.runCompile(options.cwd)\n }\n console.log('[fluenti] Extracting and compiling... done')\n options.onSuccess?.()\n return\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e))\n if (options.throwOnError) throw error\n console.warn('[fluenti] Extract/compile failed:', error.message)\n options.onError?.(error)\n return\n }\n }\n\n const msg =\n '[fluenti] @fluenti/cli is required for auto-compile.\\n' +\n ' Install it as a devDependency:\\n' +\n ' pnpm add -D @fluenti/cli\\n' +\n ' See: https://fluenti.dev/start/introduction/'\n if (options.throwOnError) {\n throw new Error(msg)\n }\n console.warn(msg)\n options.onError?.(new Error(msg))\n}\n\n/**\n * Create a debounced runner that collapses rapid calls.\n *\n * - If called while idle, schedules a run after `delay` ms.\n * - If called while a run is in progress, marks a pending rerun.\n * - Never runs concurrently.\n */\nexport function createDebouncedRunner(\n options: DevRunnerOptions,\n delay = 300,\n): () => void {\n let timer: ReturnType<typeof setTimeout> | null = null\n let running = false\n let pendingRerun = false\n\n async function execute(): Promise<void> {\n running = true\n try {\n await runExtractCompile(options)\n } finally {\n running = false\n if (pendingRerun) {\n pendingRerun = false\n schedule()\n }\n }\n }\n\n function schedule(): void {\n if (timer !== null) {\n clearTimeout(timer)\n }\n timer = setTimeout(() => {\n timer = null\n if (running) {\n pendingRerun = true\n } else {\n execute()\n }\n }, delay)\n }\n\n return schedule\n}\n","import { watch } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { createRequire } from 'node:module'\nimport { createDebouncedRunner } from './dev-runner'\n\nexport interface DevWatcherOptions {\n cwd: string\n compiledDir: string\n delay?: number\n /** Glob patterns from fluenti.config.ts `include` field */\n include?: string[]\n /** Glob patterns from fluenti.config.ts `exclude` field */\n exclude?: string[]\n /** Enable parallel compilation across locales using worker threads */\n parallelCompile?: boolean\n}\n\nlet activeWatcher: (() => void) | null = null\n\n/**\n * Extract watch directories from include glob patterns.\n *\n * Takes the static prefix before the first glob wildcard (`*`).\n * Falls back to `src` if no include patterns are provided.\n *\n * @example\n * extractWatchDirs('/proj', ['./src/**\\/*.tsx']) → ['/proj/src']\n * extractWatchDirs('/proj', ['./app/**\\/*.ts', './lib/**\\/*.ts']) → ['/proj/app', '/proj/lib']\n * extractWatchDirs('/proj', ['./**\\/*.ts']) → ['/proj']\n */\nexport function extractWatchDirs(cwd: string, include?: string[]): string[] {\n if (!include || include.length === 0) {\n return [resolve(cwd, 'src')]\n }\n\n const dirs = new Set<string>()\n for (const pattern of include) {\n const normalized = pattern.replace(/^\\.\\//, '')\n const staticPart = normalized.split('*')[0]!.replace(/\\/+$/, '')\n dirs.add(staticPart || '.')\n }\n return [...dirs].map(d => resolve(cwd, d))\n}\n\n/**\n * Start a standalone file watcher for dev auto-compile.\n *\n * Works independently of webpack/Turbopack — watches source directories\n * (inferred from `include` patterns) for changes and triggers\n * extract+compile via the debounced runner.\n *\n * Only one watcher is active at a time (guards against multiple `applyFluenti()` calls).\n *\n * @returns A cleanup function that stops the watcher.\n */\nexport function startDevWatcher(options: DevWatcherOptions): () => void {\n // Clean up previous watcher if one exists (e.g., dev server reload)\n if (activeWatcher) {\n activeWatcher()\n }\n\n const { cwd, compiledDir, delay = 1000, include, exclude, parallelCompile } = options\n const compiledDirResolved = resolve(cwd, compiledDir)\n const _require = createRequire(\n typeof __filename !== 'undefined' ? __filename : import.meta.url,\n )\n const picomatch = _require('picomatch') as (patterns: string[]) => (str: string) => boolean\n const isExcluded = exclude?.length ? picomatch(exclude) : () => false\n const runnerOpts: Parameters<typeof createDebouncedRunner>[0] = { cwd }\n if (parallelCompile) runnerOpts.parallelCompile = true\n const debouncedRun = createDebouncedRunner(runnerOpts, delay)\n\n // Initial run\n debouncedRun()\n\n const watchDirs = extractWatchDirs(cwd, include)\n const watchers = watchDirs.map(dir =>\n watch(dir, { recursive: true }, (_event, filename) => {\n if (!filename) return\n if (!/\\.[jt]sx?$/.test(filename)) return\n if (filename.includes('node_modules') || filename.includes('.next')) return\n const full = resolve(dir, filename)\n if (full.startsWith(compiledDirResolved)) return\n if (isExcluded(filename)) return\n debouncedRun()\n }),\n )\n\n const cleanup = (): void => {\n for (const w of watchers) w.close()\n activeWatcher = null\n }\n\n activeWatcher = cleanup\n return cleanup\n}\n","import { existsSync } from 'node:fs'\nimport { execSync } from 'node:child_process'\nimport { resolve, dirname, relative } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { WithFluentConfig } from './types'\nimport { resolveConfig } from './read-config'\nimport { generateServerModule } from './generate-server-module'\nimport { startDevWatcher } from './dev-watcher'\n\n// Use Record<string, any> to accept both Next 15's and 16's NextConfig types\n// (Next 16 removed the index signature from NextConfig)\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype NextConfig = Record<string, any>\n\n/**\n * Wrap your Next.js config with Fluenti support.\n *\n * Adds a webpack loader that transforms `t\\`\\`` and `t()` calls,\n * and generates a server module for RSC i18n.\n *\n * @example\n * ```ts\n * // next.config.ts — function style (recommended)\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti()({ reactStrictMode: true })\n * ```\n *\n * @example\n * ```ts\n * // next.config.ts — direct style\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti({ reactStrictMode: true })\n * ```\n */\nexport function withFluenti(fluentConfig?: WithFluentConfig): (nextConfig?: NextConfig) => NextConfig\nexport function withFluenti(nextConfig: NextConfig): NextConfig\nexport function withFluenti(\n configOrNext?: WithFluentConfig | NextConfig,\n): NextConfig | ((nextConfig?: NextConfig) => NextConfig) {\n if (configOrNext && !isFluentConfig(configOrNext as Record<string, unknown>)) {\n // Has keys but none are fluent-specific → treat as NextConfig\n return applyFluenti({}, configOrNext as NextConfig)\n }\n\n const fluentConfig = (configOrNext ?? {}) as WithFluentConfig\n return function wrappedConfig(nextConfig?: NextConfig): NextConfig {\n return applyFluenti(fluentConfig, nextConfig ?? {})\n }\n}\n\nfunction isFluentConfig(obj: Record<string, unknown>): boolean {\n const fluentOnlyKeys = [\n 'config', 'serverModule', 'serverModuleOutDir', 'resolveLocale',\n 'cookieName', 'loaderEnforce',\n ]\n return fluentOnlyKeys.some((key) => key in obj)\n}\n\nfunction applyFluenti(\n fluentConfig: WithFluentConfig,\n nextConfig: NextConfig,\n): NextConfig {\n const projectRoot = process.cwd()\n const resolved = resolveConfig(projectRoot, fluentConfig)\n const fluentiConfig = resolved.fluentiConfig\n const compiledDir = fluentiConfig.compileOutDir\n\n // Warn if compiled catalogs directory doesn't exist yet\n const compiledDirAbs = resolve(projectRoot, compiledDir)\n if (!existsSync(compiledDirAbs)) {\n console.warn(\n `\\n[fluenti] Compiled catalogs not found at ${compiledDir}.\\n` +\n `Run: npx fluenti extract && npx fluenti compile\\n`,\n )\n }\n\n // Generate server module for RSC\n const serverModulePath = generateServerModule(projectRoot, resolved)\n\n // Resolve the loader path — use import.meta.url for ESM compatibility\n const thisDir = typeof __dirname !== 'undefined'\n ? __dirname\n : dirname(fileURLToPath(import.meta.url))\n const loaderPath = resolve(thisDir, 'loader.js')\n\n const existingWebpack = nextConfig['webpack'] as\n | ((config: WebpackConfig, options: WebpackOptions) => WebpackConfig)\n | undefined\n\n let buildCompileRan = false\n\n // ── Turbopack config ──────────────────────────────────────────────\n // Turbopack loader-runner supports webpack loaders via turbopack.rules.\n // Use package name (not file path) — Turbopack resolves loaders as packages,\n // file paths trigger static analysis errors (TP1006) in the loader-runner.\n const fluentTurboRules = Object.fromEntries(\n ['*.ts', '*.tsx', '*.js', '*.jsx'].map((ext) => [\n ext,\n {\n condition: { not: 'foreign' },\n loaders: ['@fluenti/next/loader'],\n },\n ]),\n )\n\n // Turbopack resolveAlias requires relative paths (absolute paths get\n // misinterpreted as \"./abs/path\"). Use \"./\" + relative-from-cwd.\n const configModulePath = resolve(dirname(serverModulePath), 'i18n-config.js')\n const relativeServerModule = './' + relative(projectRoot, serverModulePath).split('\\\\').join('/')\n const relativeConfigModule = './' + relative(projectRoot, configModulePath).split('\\\\').join('/')\n const fluentTurboAlias: Record<string, string> = {\n '@fluenti/next': relativeServerModule,\n '@fluenti/next/i18n-config': relativeConfigModule,\n }\n\n // ── Build auto-compile (bundler-agnostic — runs at config time) ──\n const isDev = process.env['NODE_ENV'] === 'development'\n || process.argv.some(a => a === 'dev')\n const buildAutoCompile = fluentiConfig.buildAutoCompile ?? true\n\n if (!isDev && buildAutoCompile && !buildCompileRan) {\n buildCompileRan = true\n try {\n execSync(\n 'npx fluenti compile' + (fluentiConfig.parallelCompile ? ' --parallel' : ''),\n { cwd: projectRoot, stdio: 'inherit' },\n )\n } catch {\n // @fluenti/cli not installed or compile failed — user sees stdio output\n }\n }\n\n // ── Dev auto-compile via standalone watcher (works with both webpack & Turbopack) ──\n const devAutoCompile = fluentiConfig.devAutoCompile ?? true\n\n if (isDev && devAutoCompile) {\n const watcherOpts: Parameters<typeof startDevWatcher>[0] = {\n cwd: projectRoot,\n compiledDir,\n delay: fluentiConfig.devAutoCompileDelay ?? 1000,\n }\n if (fluentiConfig.parallelCompile) watcherOpts.parallelCompile = true\n if (fluentiConfig.include) watcherOpts.include = fluentiConfig.include\n if (fluentiConfig.exclude) watcherOpts.exclude = fluentiConfig.exclude\n startDevWatcher(watcherOpts)\n }\n\n return {\n ...nextConfig,\n turbopack: mergeTurbopackConfig(\n nextConfig['turbopack'] as Record<string, unknown> | undefined,\n { rules: fluentTurboRules, resolveAlias: fluentTurboAlias },\n ),\n webpack(config: WebpackConfig, options: WebpackOptions) {\n // Add fluenti loader\n const loaderEnforce = fluentConfig.loaderEnforce === undefined && !('loaderEnforce' in (fluentConfig as Record<string, unknown>))\n ? 'pre' as const\n : fluentConfig.loaderEnforce\n config.module.rules.push({\n test: /\\.[jt]sx?$/,\n ...(loaderEnforce ? { enforce: loaderEnforce } : {}),\n exclude: [/node_modules/, /\\.next/],\n use: [\n {\n loader: loaderPath,\n options: {\n serverModulePath,\n },\n },\n ],\n })\n\n // Add resolve alias so loader can import from generated server module\n config.resolve = config.resolve ?? {} as WebpackConfig['resolve']\n config.resolve.alias = config.resolve.alias ?? {}\n config.resolve.alias['@fluenti/next$'] = serverModulePath\n config.resolve.alias['@fluenti/next/i18n-config$'] = configModulePath\n\n // Call user's webpack config if provided\n if (existingWebpack) {\n return existingWebpack(config, options)\n }\n\n return config\n },\n }\n}\n\nfunction mergeTurbopackConfig(\n existing: Record<string, unknown> | undefined,\n fluenti: { rules: Record<string, unknown>; resolveAlias: Record<string, string> },\n): Record<string, unknown> {\n const base = existing ?? {}\n const userRules = (base['rules'] as Record<string, unknown>) ?? {}\n\n // Warn when user rules override fluenti's source-file rules\n const fluentKeys = Object.keys(fluenti.rules)\n const overlapping = fluentKeys.filter(k => k in userRules)\n if (overlapping.length > 0) {\n console.warn(\n `[fluenti] Your turbopack.rules override Fluenti's loader for: ${overlapping.join(', ')}.\\n` +\n ` Fluenti's t\\`\\` transform will NOT run on these file types.\\n` +\n ` If this is intentional, you can suppress this warning with { devAutoCompile: false }.`,\n )\n }\n\n return {\n ...base,\n rules: { ...fluenti.rules, ...userRules },\n resolveAlias: { ...fluenti.resolveAlias, ...(base['resolveAlias'] as Record<string, string>) },\n }\n}\n\n// Minimal webpack types for the config function\ninterface WebpackConfig {\n module: {\n rules: Array<Record<string, unknown>>\n }\n resolve: {\n alias?: Record<string, string>\n }\n}\n\ninterface WebpackOptions {\n isServer: boolean\n dev: boolean\n}\n","/**\n * @fluenti/next — Next.js plugin for Fluenti\n *\n * Provides:\n * - `withFluenti()` — wraps next.config.ts with t`` transform support\n * - I18nProvider — async server component (exported from generated module)\n * - Webpack loader for strict, binding-aware tagged-template optimization\n *\n * @example\n * ```ts\n * // next.config.ts\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti()({ reactStrictMode: true })\n * ```\n *\n * @example\n * ```tsx\n * // app/layout.tsx — resolved by webpack alias to the generated module\n * import { I18nProvider } from '@fluenti/next'\n * ```\n */\nexport { withFluenti } from './with-fluenti'\nexport type { WithFluentConfig, I18nProviderProps } from './types'\nexport { msg } from '@fluenti/react'\n\n// ── Runtime stubs ────────────────────────────────────────────────────\n// TypeScript resolves types from this file (via package.json exports).\n// At runtime, webpack `resolve.alias` redirects `@fluenti/next$` to the\n// generated server module, so these stubs are never actually called in\n// a correctly configured project. They exist only to provide helpful\n// errors if `withFluenti()` is not configured.\n\nimport type { ReactNode, ReactElement } from 'react'\nimport type { CompileTimeT, FluentiCoreInstanceFull } from '@fluenti/core'\n\nconst NOT_CONFIGURED =\n '[fluenti] `withFluenti()` must be configured in next.config.ts before importing from \"@fluenti/next\".'\n\nfunction throwNotConfigured(): never {\n throw new Error(NOT_CONFIGURED)\n}\n\n/** @see Generated module for the real implementation. */\nexport const setLocale: (locale: string) => void = throwNotConfigured\n/** @see Generated module for the real implementation. */\nexport const getI18n: () => Promise<FluentiCoreInstanceFull & { locale: string }> = throwNotConfigured as () => Promise<FluentiCoreInstanceFull & { locale: string }>\n/** @see Generated module for the real implementation. */\nexport const t: CompileTimeT = throwNotConfigured as unknown as CompileTimeT\n/** @see Generated module for the real implementation. */\nexport const Trans: (props: { children: ReactNode; id?: string; context?: string; comment?: string; render?: (translation: ReactNode) => ReactNode }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Trans\n/** @see Generated module for the real implementation. */\nexport const Plural: (props: { value: number; id?: string; context?: string; comment?: string; zero?: ReactNode; one?: ReactNode; two?: ReactNode; few?: ReactNode; many?: ReactNode; other: ReactNode; offset?: number }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Plural\n/** @see Generated module for the real implementation. */\nexport const Select: (props: { value: string; id?: string; context?: string; comment?: string; other: ReactNode; options?: Record<string, ReactNode>; [key: string]: ReactNode | Record<string, ReactNode> | string | undefined }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof Select\n/** @see Generated module for the real implementation. */\nexport const DateTime: (props: { value: Date | number; style?: string }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof DateTime\n/** @see Generated module for the real implementation. */\nexport const NumberFormat: (props: { value: number; style?: string }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof NumberFormat\n/** @see Generated module for the real implementation. */\nexport const I18nProvider: (props: { locale?: string; children: ReactNode }) => Promise<ReactElement> = throwNotConfigured as unknown as typeof I18nProvider\n"],"mappings":";;;;;;;;;;AASA,SAAgB,EACd,GACA,GACsB;CACtB,IAAI;AAEJ,CAKE,IALE,GAAW,UAAU,OAAO,EAAU,UAAW,WAEnC;EAAE,GAAG;EAAwB,GAAG,EAAU;EAAQ,GAGlD,EACd,OAAO,GAAW,UAAW,WAAW,EAAU,SAAS,KAAA,GAC3D,EACD;CAGH,IAAM,IAAqB,GAAW,sBAAsB,YACtD,IAAa,GAAW,cAAc,UAEtC,IAAiC;EACrC;EACA,cAAc,GAAW,gBAAgB;EACzC;EACA;EACD;AAED,QADI,GAAW,kBAAe,EAAS,gBAAgB,EAAU,gBAC1D;;;;AC7BT,SAAS,EAAU,GAAc,GAAuB;AACtD,KAAI;AACF,IAAc,GAAM,GAAS,QAAQ;UAC9B,GAAK;AACZ,QAAU,MACR,iDAAiD,EAAK,IAAI,aAAe,QAAQ,EAAI,UAAU,OAAO,EAAI,GAC3G;;;AAYL,SAAgB,EACd,GACA,GACQ;AACR,KAAI,EAAO,aACT,QAAO,EAAQ,GAAa,EAAO,aAAa;CAGlD,IAAM,IAAS,EAAQ,GAAa,EAAO,mBAAmB,EACxD,IAAU,EAAQ,GAAQ,YAAY,EACtC,IAAU,EAAQ,GAAQ,cAAc;AAE9C,CAAK,EAAW,EAAO,IACrB,EAAU,GAAQ,EAAE,WAAW,IAAM,CAAC;CAGxC,IAAM,IAAU,EAAmB,EAAO,cAAc,QAAQ,EAC1D,IAAgB,EAAO,cAAc,iBAAiB,EAAO,cAAc,cAC3E,IAAc,EAAO,cAAc,eACnC,IAAgB,EAAO,cAAc,eACrC,IAAc,EAAO,cAAc,aACnC,IAAgB,EAAO,cAAc,eACrC,IAAa,EAAqB,EAAO,WAAW,EACpD,IAAoB,EAAqB,EAAc;AAE7D,MAAK,IAAM,KAAU,EACnB,GAAe,GAAQ,cAAc;CAIvC,IAAM,IAAmB,EAAe,EAAS,GAD1B,EAAQ,GAAa,EAAY,CACgB,CAAC,EAEnE,IAAgB,EACnB,KAAK,MAAW,eAAe,EAAqB,EAAO,CAAC,oBAAoB,EAAiB,GAAG,EAAO,IAAI,CAC/G,KAAK,KAAK,EAEP,IAAmB,IACrB,KAAK,UAAU,EAAc,GAC7B;AAoDJ,CAbA,EAnC2B,EAAQ,GAAQ,qBAAqB,EAmBnC;;;;;;EAjBD,EACzB,KAAK,MAEG,UADM,EAAO,QAAQ,iBAAiB,IAAI,CAC3B,SAAS,EAAiB,GAAG,EAAO,GAC1D,CACD,KAAK,KAAK,CAkBO;;0BAhBa,EAC9B,KAAK,MAAW;EACf,IAAM,IAAO,EAAO,QAAQ,iBAAiB,IAAI;AACjD,SAAO,IAAI,EAAqB,EAAO,CAAC,KAAK;GAC7C,CACD,KAAK,KAAK,CAaoC;wBAX1B,MAAgB,KAAA,IAA0C,cAA9B,KAAK,UAAU,EAAY,CAYzC;0BAXZ,MAAkB,KAAA,IAA4C,cAAhC,KAAK,UAAU,EAAc,CAY3C;;;;;EAMU,EAanD,EAX8B,EAAQ,GAAQ,uBAAuB,EACrC,mSAUyB;CAEzD,IAAM,IAAsB,EAAO,gBAItB,gCADS,EAAe,EAAS,GADxB,EAAQ,GAAa,EAAO,cAAc,CACF,CAAC,CACV,KAEjD,MAEE,IAAkB,EAAO,gBAC3B,oCACA;;;;;;;;;;4CAUsC,EAAW;;;;;;;;;;;;;;;gBAevC,EAAkB;;gBAElB,EAAkB;;OAI1B,IAAkB,KAAK,UAAU,EAAQ;AAyH/C,CADA,EAAU,GAtHW;;;;;EAKrB,IAAsB,GAAG,EAAoB,MAAM,GAAG;oBACpC,EAAgB;;;;;;EAMlC,EAAc;gCACgB,EAAiB,GAAG,EAAkB;;;qBAGjD,EAAkB;mBACpB,EAAiB;IAChC,EAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;+DA0B2C,EAAkB;;;;;;;;;;;;uBAY1D,EAAkB;qBACpB,EAAiB;;;EA6DJ,EAChC,EAAU,GAAS,47CAAU;CAG7B,IAAM,IAAmB,EAAQ,GAAQ,iBAAiB,EACpD,IAAsB,EAAQ,GAAQ,mBAAmB;AAgB/D,QATA,EAAU,GALiB;yBACJ,EAAgB;+BACV,EAAkB;6BACpB,EAAW;EAES,EAO/C,EAAU,GALoB,iLAKuB,EAE9C;;AAIT,SAAS,EAAqB,GAAmB;AAC/C,QAAO,EACJ,QAAQ,OAAO,OAAO,CACtB,QAAQ,MAAM,MAAM,CACpB,QAAQ,OAAO,MAAM,CACrB,QAAQ,OAAO,MAAM,CACrB,QAAQ,OAAO,MAAM;;AAG1B,SAAS,EAAe,GAAmB;AACzC,QAAO,EAAE,MAAM,KAAK,CAAC,KAAK,IAAI;;;;ACrShC,eAAsB,EAAkB,GAA0C;AAChF,KAAI,EAAQ,YACV,KAAI;EAMF,IAAM,EAAE,kBADe,EAAc,EAAK,EAAQ,KAAK,eAAe,CAAC,CACjC,eAAe;AAGrD,EAFA,MAAM,EAAW,EAAQ,IAAI,EAC7B,QAAQ,IAAI,8BAA8B,EAC1C,EAAQ,aAAa;AACrB;UACO,GAAG;EACV,IAAM,IAAQ,aAAa,QAAQ,IAAQ,MAAM,OAAO,EAAE,CAAC;AAC3D,MAAI,EAAQ,aAAc,OAAM;AAEhC,EADA,QAAQ,KAAK,6BAA6B,EAAM,QAAQ,EACxD,EAAQ,UAAU,EAAM;AACxB;;CAOJ,IAAI,IAA6I;AACjJ,KAAI;AAEF,MADuB,EAAc,EAAK,EAAQ,KAAK,eAAe,CAAC,CAC5C,eAAe;SACpC;AAIR,KAAI,EACF,KAAI;AAQF,EAPA,MAAM,EAAU,WAAW,EAAQ,IAAI,EACnC,EAAQ,kBACV,MAAM,EAAU,WAAW,EAAQ,KAAK,EAAE,UAAU,IAAM,CAAC,GAE3D,MAAM,EAAU,WAAW,EAAQ,IAAI,EAEzC,QAAQ,IAAI,6CAA6C,EACzD,EAAQ,aAAa;AACrB;UACO,GAAG;EACV,IAAM,IAAQ,aAAa,QAAQ,IAAQ,MAAM,OAAO,EAAE,CAAC;AAC3D,MAAI,EAAQ,aAAc,OAAM;AAEhC,EADA,QAAQ,KAAK,qCAAqC,EAAM,QAAQ,EAChE,EAAQ,UAAU,EAAM;AACxB;;CAIJ,IAAM,IACJ;AAIF,KAAI,EAAQ,aACV,OAAU,MAAM,EAAI;AAGtB,CADA,QAAQ,KAAK,EAAI,EACjB,EAAQ,UAAc,MAAM,EAAI,CAAC;;AAUnC,SAAgB,EACd,GACA,IAAQ,KACI;CACZ,IAAI,IAA8C,MAC9C,IAAU,IACV,IAAe;CAEnB,eAAe,IAAyB;AACtC,MAAU;AACV,MAAI;AACF,SAAM,EAAkB,EAAQ;YACxB;AAER,GADA,IAAU,IACN,MACF,IAAe,IACf,GAAU;;;CAKhB,SAAS,IAAiB;AAIxB,EAHI,MAAU,QACZ,aAAa,EAAM,EAErB,IAAQ,iBAAiB;AAEvB,GADA,IAAQ,MACJ,IACF,IAAe,KAEf,GAAS;KAEV,EAAM;;AAGX,QAAO;;;;AC9GT,IAAI,IAAqC;AAazC,SAAgB,EAAiB,GAAa,GAA8B;AAC1E,KAAI,CAAC,KAAW,EAAQ,WAAW,EACjC,QAAO,CAAC,EAAQ,GAAK,MAAM,CAAC;CAG9B,IAAM,oBAAO,IAAI,KAAa;AAC9B,MAAK,IAAM,KAAW,GAAS;EAE7B,IAAM,IADa,EAAQ,QAAQ,SAAS,GAAG,CACjB,MAAM,IAAI,CAAC,GAAI,QAAQ,QAAQ,GAAG;AAChE,IAAK,IAAI,KAAc,IAAI;;AAE7B,QAAO,CAAC,GAAG,EAAK,CAAC,KAAI,MAAK,EAAQ,GAAK,EAAE,CAAC;;AAc5C,SAAgB,EAAgB,GAAwC;AAEtE,CAAI,KACF,GAAe;CAGjB,IAAM,EAAE,QAAK,gBAAa,WAAQ,KAAM,YAAS,YAAS,uBAAoB,GACxE,IAAsB,EAAQ,GAAK,EAAY,EAI/C,IAHW,EACf,OAAO,aAAe,MAAc,aAAa,OAAO,KAAK,IAC9D,CAC0B,YAAY,EACjC,IAAa,GAAS,SAAS,EAAU,EAAQ,SAAS,IAC1D,IAA0D,EAAE,QAAK;AACvE,CAAI,MAAiB,EAAW,kBAAkB;CAClD,IAAM,IAAe,EAAsB,GAAY,EAAM;AAG7D,IAAc;CAGd,IAAM,IADY,EAAiB,GAAK,EAAQ,CACrB,KAAI,MAC7B,EAAM,GAAK,EAAE,WAAW,IAAM,GAAG,GAAQ,MAAa;AAC/C,OACA,aAAa,KAAK,EAAS,KAC5B,EAAS,SAAS,eAAe,IAAI,EAAS,SAAS,QAAQ,IACtD,EAAQ,GAAK,EAAS,CAC1B,WAAW,EAAoB,IACpC,EAAW,EAAS,IACxB,GAAc;GACd,CACH,EAEK,UAAsB;AAC1B,OAAK,IAAM,KAAK,EAAU,GAAE,OAAO;AACnC,MAAgB;;AAIlB,QADA,IAAgB,GACT;;;;AC1DT,SAAgB,EACd,GACwD;AACxD,KAAI,KAAgB,CAAC,EAAe,EAAwC,CAE1E,QAAO,EAAa,EAAE,EAAE,EAA2B;CAGrD,IAAM,IAAgB,KAAgB,EAAE;AACxC,QAAO,SAAuB,GAAqC;AACjE,SAAO,EAAa,GAAc,KAAc,EAAE,CAAC;;;AAIvD,SAAS,EAAe,GAAuC;AAK7D,QAJuB;EACrB;EAAU;EAAgB;EAAsB;EAChD;EAAc;EACf,CACqB,MAAM,MAAQ,KAAO,EAAI;;AAGjD,SAAS,EACP,GACA,GACY;CACZ,IAAM,IAAc,QAAQ,KAAK,EAC3B,IAAW,EAAc,GAAa,EAAa,EACnD,IAAgB,EAAS,eACzB,IAAc,EAAc;AAIlC,CAAK,EADkB,EAAQ,GAAa,EAAY,CACzB,IAC7B,QAAQ,KACN,8CAA8C,EAAY,sDAE3D;CAIH,IAAM,IAAmB,EAAqB,GAAa,EAAS,EAM9D,IAAa,EAHH,OAAO,YAAc,MACjC,YACA,EAAQ,EAAc,OAAO,KAAK,IAAI,CAAC,EACP,YAAY,EAE1C,IAAkB,EAAW,SAI/B,IAAkB,IAMhB,IAAmB,OAAO,YAC9B;EAAC;EAAQ;EAAS;EAAQ;EAAQ,CAAC,KAAK,MAAQ,CAC9C,GACA;EACE,WAAW,EAAE,KAAK,WAAW;EAC7B,SAAS,CAAC,uBAAuB;EAClC,CACF,CAAC,CACH,EAIK,IAAmB,EAAQ,EAAQ,EAAiB,EAAE,iBAAiB,EACvE,IAAuB,OAAO,EAAS,GAAa,EAAiB,CAAC,MAAM,KAAK,CAAC,KAAK,IAAI,EAC3F,IAAuB,OAAO,EAAS,GAAa,EAAiB,CAAC,MAAM,KAAK,CAAC,KAAK,IAAI,EAC3F,IAA2C;EAC/C,iBAAiB;EACjB,6BAA6B;EAC9B,EAGK,IAAA,QAAA,IAAA,aAAoC,iBACrC,QAAQ,KAAK,MAAK,MAAK,MAAM,MAAM,EAClC,IAAmB,EAAc,oBAAoB;AAE3D,KAAI,CAAC,KAAS,KAAoB,CAAC,GAAiB;AAClD,MAAkB;AAClB,MAAI;AACF,KACE,yBAAyB,EAAc,kBAAkB,gBAAgB,KACzE;IAAE,KAAK;IAAa,OAAO;IAAW,CACvC;UACK;;CAMV,IAAM,IAAiB,EAAc,kBAAkB;AAEvD,KAAI,KAAS,GAAgB;EAC3B,IAAM,IAAqD;GACzD,KAAK;GACL;GACA,OAAO,EAAc,uBAAuB;GAC7C;AAID,EAHI,EAAc,oBAAiB,EAAY,kBAAkB,KAC7D,EAAc,YAAS,EAAY,UAAU,EAAc,UAC3D,EAAc,YAAS,EAAY,UAAU,EAAc,UAC/D,EAAgB,EAAY;;AAG9B,QAAO;EACL,GAAG;EACH,WAAW,EACT,EAAW,WACX;GAAE,OAAO;GAAkB,cAAc;GAAkB,CAC5D;EACD,QAAQ,GAAuB,GAAyB;GAEtD,IAAM,IAAgB,EAAa,kBAAkB,KAAA,KAAa,EAAE,mBAAoB,KACpF,QACA,EAAa;AA0BjB,UAzBA,EAAO,OAAO,MAAM,KAAK;IACvB,MAAM;IACN,GAAI,IAAgB,EAAE,SAAS,GAAe,GAAG,EAAE;IACnD,SAAS,CAAC,gBAAgB,SAAS;IACnC,KAAK,CACH;KACE,QAAQ;KACR,SAAS,EACP,qBACD;KACF,CACF;IACF,CAAC,EAGF,EAAO,UAAU,EAAO,WAAW,EAAE,EACrC,EAAO,QAAQ,QAAQ,EAAO,QAAQ,SAAS,EAAE,EACjD,EAAO,QAAQ,MAAM,oBAAoB,GACzC,EAAO,QAAQ,MAAM,gCAAgC,GAGjD,IACK,EAAgB,GAAQ,EAAQ,GAGlC;;EAEV;;AAGH,SAAS,EACP,GACA,GACyB;CACzB,IAAM,IAAO,KAAY,EAAE,EACrB,IAAa,EAAK,SAAwC,EAAE,EAI5D,IADa,OAAO,KAAK,EAAQ,MAAM,CACd,QAAO,MAAK,KAAK,EAAU;AAS1D,QARI,EAAY,SAAS,KACvB,QAAQ,KACN,iEAAiE,EAAY,KAAK,KAAK,CAAC,2JAGzF,EAGI;EACL,GAAG;EACH,OAAO;GAAE,GAAG,EAAQ;GAAO,GAAG;GAAW;EACzC,cAAc;GAAE,GAAG,EAAQ;GAAc,GAAI,EAAK;GAA4C;EAC/F;;;;AC/KH,IAAM,IACJ;AAEF,SAAS,IAA4B;AACnC,OAAU,MAAM,EAAe;;AAIjC,IAAa,IAAsC,GAEtC,IAAuE,GAEvE,IAAkB,GAElB,IAAoK,GAEpK,IAAyO,GAEzO,IAAiP,GAEjP,IAAuF,GAEvF,IAAoF,GAEpF,IAA2F"}
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@fluenti/next/i18n-config`);var t=`x-fluenti-locale`;function n(n){let{NextResponse:o}=n,s=n.locales??e.locales,c=n.sourceLocale??e.sourceLocale,l=n.cookieName??e.cookieName,u=n.localePrefix??`as-needed`,d=n.rewriteDefaultLocale??!1,f=n.setCookie??!1;return function(e){let p=s,m=c,{pathname:h,search:g}=e.nextUrl,_=e.nextUrl.basePath??``,v=h.split(`/`),y=r(v[1]??``,p),b;if(y)b=y;else{let t=n.detectLocale?.(e);b=t!==void 0&&r(t,p)!==null?r(t,p):a(e,p,m,l)}let x=new Headers(e.headers);if(x.set(t,b),!y&&(u===`always`||b!==m)){let n=new URL(`${_}/${b}${h}${g}`,e.url),r=o.redirect(n);return r.headers.set(t,b),i(r,e,b,l,f,y),r}if(!y&&b===m&&d){let n=new URL(`${_}/${m}${h}${g}`,e.url),r=o.rewrite(n,{request:{headers:x}});return r.headers.set(t,b),i(r,e,b,l,f,y),r}if(u===`as-needed`&&y===m){let n=(`/`+v.slice(2).join(`/`)).replace(/\/+/g,`/`);if(d){let r=new URL(`${_}${n}${g}`,e.url),i=o.redirect(r);return i.headers.set(t,b),i}let r=new URL(`${_}${n}${g}`,e.url),i=o.rewrite(r,{request:{headers:x}});return i.headers.set(t,b),i}let S=o.next({request:{headers:x}});return S.headers.set(t,b),i(S,e,b,l,f,y),S}}function r(e,t){if(!e)return null;let n=e.toLowerCase();return t.find(e=>e.toLowerCase()===n)??null}function i(e,t,n,r,i,a){!i||a||t.cookies.get(r)?.value!==n&&e.headers.set(`set-cookie`,`${r}=${n};path=/;max-age=31536000;samesite=lax`)}function a(e,t,n,i){let a=e.cookies.get(i)?.value;if(a){let e=r(a,t);if(e)return e}let o=e.headers.get(`accept-language`);if(o)for(let e of o.split(`,`)){let n=e.split(`;`)[0].trim(),i=r(n,t);if(i)return i;let a=n.split(`-`)[0].toLowerCase(),o=t.find(e=>{let t=e.toLowerCase();return t===a||t.startsWith(a+`-`)});if(o)return o}return n}exports.LOCALE_HEADER=t,exports.createI18nMiddleware=n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@fluenti/next/i18n-config`);var t=`x-fluenti-locale`;function n(n){let{NextResponse:o}=n,s=n.locales??e.locales,c=n.sourceLocale??e.sourceLocale,l=n.cookieName??e.cookieName,u=n.localePrefix??`as-needed`,d=n.rewriteDefaultLocale??!1,f=n.setCookie??!1;return function(e){let p=s,m=c,{pathname:h,search:g}=e.nextUrl,_=e.nextUrl.basePath??``,v=h.split(`/`),y=r(v[1]??``,p),b;if(y)b=y;else{let t=n.detectLocale?.(e);b=t!==void 0&&r(t,p)!==null?r(t,p):a(e,p,m,l)}let x=new Headers(e.headers);if(x.set(t,b),!y&&(u===`always`||b!==m)){let n=new URL(`${_}/${b}${h}${g}`,e.url),r=o.redirect(n);return r.headers.set(t,b),i(r,e,b,l,f,y),r}if(!y&&b===m&&d){let n=new URL(`${_}/${m}${h}${g}`,e.url),r=o.rewrite(n,{request:{headers:x}});return r.headers.set(t,b),i(r,e,b,l,f,y),r}if(u===`as-needed`&&y===m){let n=(`/`+v.slice(2).join(`/`)).replace(/\/+/g,`/`);if(d){let r=new URL(`${_}${n}${g}`,e.url),i=o.redirect(r);return i.headers.set(t,b),i}let r=new URL(`${_}${n}${g}`,e.url),i=o.rewrite(r,{request:{headers:x}});return i.headers.set(t,b),i}let S=o.next({request:{headers:x}});return S.headers.set(t,b),i(S,e,b,l,f,y),S}}function r(e,t){if(!e)return null;let n=e.toLowerCase();return t.find(e=>e.toLowerCase()===n)??null}function i(e,t,n,r,i,a){!i||a||t.cookies.get(r)?.value!==n&&e.headers.set(`set-cookie`,`${r}=${encodeURIComponent(n)};path=/;max-age=31536000;samesite=lax`)}function a(e,t,n,i){let a=e.cookies.get(i)?.value;if(a){let e=r(a,t);if(e)return e}let o=e.headers.get(`accept-language`);if(o)for(let e of o.split(`,`)){let n=e.split(`;`)[0].trim(),i=r(n,t);if(i)return i;let a=n.split(`-`)[0].toLowerCase(),o=t.find(e=>{let t=e.toLowerCase();return t===a||t.startsWith(a+`-`)});if(o)return o}return n}exports.LOCALE_HEADER=t,exports.createI18nMiddleware=n;
2
2
  //# sourceMappingURL=middleware.cjs.map
@@ -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 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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"}
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}=${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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"}
@@ -38,7 +38,7 @@ function a(e, t) {
38
38
  return t.find((e) => e.toLowerCase() === n) ?? null;
39
39
  }
40
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`);
41
+ !i || a || t.cookies.get(r)?.value !== n && e.headers.set("set-cookie", `${r}=${encodeURIComponent(n)};path=/;max-age=31536000;samesite=lax`);
42
42
  }
43
43
  function s(e, t, n, r) {
44
44
  let i = e.cookies.get(r)?.value;
@@ -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}=${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 * 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,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=>{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;
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;
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 // 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"}
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 +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;;;;EAkBxC"}
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"}
@@ -12,7 +12,11 @@ function a(a) {
12
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";
13
13
  return {
14
14
  switchLocale: (e) => {
15
- document.cookie = `${p}=${e};path=/;max-age=31536000;samesite=lax`, l(e);
15
+ if (!d.includes(e)) {
16
+ typeof process < "u" && process.env.NODE_ENV !== "production" && console.warn(`[fluenti] switchLocale: invalid locale "${e}"`);
17
+ return;
18
+ }
19
+ document.cookie = `${p}=${encodeURIComponent(e)};path=/;max-age=31536000;samesite=lax`, l(e);
16
20
  let t = i(s, e, {
17
21
  sourceLocale: f,
18
22
  locales: d,
@@ -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 // 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"}
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 +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,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"}
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 for Next.js App Router.\n *\n * Wraps `@fluenti/react`'s `I18nProvider` with the `'use client'` directive,\n * enabling hydration of client components in a Server Component tree.\n * Re-exported as `I18nProvider` from `@fluenti/next/provider`.\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * import { I18nProvider } from '@fluenti/next/provider'\n * import en from '../locales/compiled/en.js'\n *\n * export default function RootLayout({ children }: { children: React.ReactNode }) {\n * return (\n * <html>\n * <body>\n * <I18nProvider\n * locale=\"en\"\n * fallbackLocale=\"en\"\n * messages={{ en }}\n * >\n * {children}\n * </I18nProvider>\n * </body>\n * </html>\n * )\n * }\n * ```\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":"kIA8CA,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","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":";;;AAoBA,SAAgB,EAAmB,EACjC,WACA,mBACA,aACA,kBACA,gBACA,kBACA,eAC0B;AAC1B,QACE,kBAAC,GAAD;EACU;EACQ;EACN;EACV,GAAK,IAAgB,EAAE,kBAAe,GAAG,EAAE;EAC3C,GAAK,IAAc,EAAE,gBAAa,GAAG,EAAE;EACvC,GAAK,IAAgB,EAAE,kBAAe,GAAG,EAAE;EAE1C;EACY,CAAA"}
1
+ {"version":3,"file":"provider.js","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 for Next.js App Router.\n *\n * Wraps `@fluenti/react`'s `I18nProvider` with the `'use client'` directive,\n * enabling hydration of client components in a Server Component tree.\n * Re-exported as `I18nProvider` from `@fluenti/next/provider`.\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * import { I18nProvider } from '@fluenti/next/provider'\n * import en from '../locales/compiled/en.js'\n *\n * export default function RootLayout({ children }: { children: React.ReactNode }) {\n * return (\n * <html>\n * <body>\n * <I18nProvider\n * locale=\"en\"\n * fallbackLocale=\"en\"\n * messages={{ en }}\n * >\n * {children}\n * </I18nProvider>\n * </body>\n * </html>\n * )\n * }\n * ```\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":";;;AA8CA,SAAgB,EAAmB,EACjC,WACA,mBACA,aACA,kBACA,gBACA,kBACA,eAC0B;AAC1B,QACE,kBAAC,GAAD;EACU;EACQ;EACN;EACV,GAAK,IAAgB,EAAE,kBAAe,GAAG,EAAE;EAC3C,GAAK,IAAc,EAAE,gBAAa,GAAG,EAAE;EACvC,GAAK,IAAgB,EAAE,kBAAe,GAAG,EAAE;EAE1C;EACY,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.cjs","names":[],"sources":["../src/with-locale.ts"],"sourcesContent":["/**\n * Per-component locale isolation for RSC.\n *\n * Temporarily switches the request-scoped locale, executes a function,\n * then restores the previous locale.\n *\n * @example\n * ```tsx\n * import { withLocale } from '@fluenti/next/server'\n *\n * export default async function Page() {\n * return (\n * <div>\n * <h1>{t`Main content`}</h1>\n * {await withLocale('ja', async () => (\n * <JapaneseWidget />\n * ))}\n * <Footer />\n * </div>\n * )\n * }\n * ```\n */\nexport async function withLocale<T>(\n locale: string,\n fn: () => T | Promise<T>,\n serverModule?: { setLocale: (l: string) => void; getI18n: () => Promise<unknown> },\n): Promise<T> {\n if (!serverModule) {\n throw new Error(\n '[fluenti] withLocale requires a server module reference. ' +\n 'Pass the generated server module as the third argument: ' +\n 'withLocale(\"ja\", fn, serverI18n)',\n )\n }\n\n // Read current locale from the module's request store\n // We need to save/restore state. Since we can't access the store directly,\n // we use setLocale to switch and rely on the caller to restore.\n const prevInstance = await serverModule.getI18n()\n const prevLocale = (prevInstance as { locale: string }).locale\n\n try {\n serverModule.setLocale(locale)\n // Force instance recreation by calling getI18n again\n await serverModule.getI18n()\n return await fn()\n } finally {\n serverModule.setLocale(prevLocale)\n await serverModule.getI18n()\n }\n}\n"],"mappings":"mEAuBA,eAAsB,EACpB,EACA,EACA,EACY,CACZ,GAAI,CAAC,EACH,MAAU,MACR,oJAGD,CAOH,IAAM,GADe,MAAM,EAAa,SAAS,EACO,OAExD,GAAI,CAIF,OAHA,EAAa,UAAU,EAAO,CAE9B,MAAM,EAAa,SAAS,CACrB,MAAM,GAAI,QACT,CACR,EAAa,UAAU,EAAW,CAClC,MAAM,EAAa,SAAS"}
1
+ {"version":3,"file":"server.cjs","names":[],"sources":["../src/with-locale.ts"],"sourcesContent":["/**\n * Per-component locale isolation for RSC.\n *\n * Temporarily switches the request-scoped locale, executes a function,\n * then restores the previous locale. This allows rendering a subtree in\n * a different locale without affecting the rest of the page.\n *\n * @param locale - The locale to switch to for the duration of `fn`.\n * @param fn - The function to execute with the switched locale.\n * @param serverModule - The generated server module reference (auto-injected\n * by the webpack loader in production; only needed when calling manually).\n * @returns The return value of `fn`.\n *\n * @example Basic usage in a Server Component\n * ```tsx\n * // app/page.tsx (Server Component)\n * import { withLocale } from '@fluenti/next/server'\n *\n * export default async function Page() {\n * return (\n * <div>\n * <h1>{t`Main content`}</h1>\n * {await withLocale('ja', async () => (\n * <JapaneseWidget />\n * ))}\n * <Footer />\n * </div>\n * )\n * }\n * ```\n *\n * @example Rendering a multilingual page section\n * ```tsx\n * import { withLocale } from '@fluenti/next/server'\n *\n * export default async function MultilingualPage() {\n * const englishContent = await withLocale('en', async () => (\n * <p>{t`Welcome`}</p>\n * ))\n * const japaneseContent = await withLocale('ja', async () => (\n * <p>{t`Welcome`}</p>\n * ))\n * return (\n * <div>\n * {englishContent}\n * {japaneseContent}\n * </div>\n * )\n * }\n * ```\n */\nexport async function withLocale<T>(\n locale: string,\n fn: () => T | Promise<T>,\n serverModule?: { setLocale: (l: string) => void; getI18n: () => Promise<unknown> },\n): Promise<T> {\n if (!serverModule) {\n throw new Error(\n '[fluenti] withLocale requires a server module reference. ' +\n 'Pass the generated server module as the third argument: ' +\n 'withLocale(\"ja\", fn, serverI18n)',\n )\n }\n\n // Read current locale from the module's request store\n // We need to save/restore state. Since we can't access the store directly,\n // we use setLocale to switch and rely on the caller to restore.\n const prevInstance = await serverModule.getI18n()\n const prevLocale = (prevInstance as { locale: string }).locale\n\n try {\n serverModule.setLocale(locale)\n // Force instance recreation by calling getI18n again\n await serverModule.getI18n()\n return await fn()\n } finally {\n serverModule.setLocale(prevLocale)\n await serverModule.getI18n()\n }\n}\n"],"mappings":"mEAmDA,eAAsB,EACpB,EACA,EACA,EACY,CACZ,GAAI,CAAC,EACH,MAAU,MACR,oJAGD,CAOH,IAAM,GADe,MAAM,EAAa,SAAS,EACO,OAExD,GAAI,CAIF,OAHA,EAAa,UAAU,EAAO,CAE9B,MAAM,EAAa,SAAS,CACrB,MAAM,GAAI,QACT,CACR,EAAa,UAAU,EAAW,CAClC,MAAM,EAAa,SAAS"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","names":[],"sources":["../src/with-locale.ts"],"sourcesContent":["/**\n * Per-component locale isolation for RSC.\n *\n * Temporarily switches the request-scoped locale, executes a function,\n * then restores the previous locale.\n *\n * @example\n * ```tsx\n * import { withLocale } from '@fluenti/next/server'\n *\n * export default async function Page() {\n * return (\n * <div>\n * <h1>{t`Main content`}</h1>\n * {await withLocale('ja', async () => (\n * <JapaneseWidget />\n * ))}\n * <Footer />\n * </div>\n * )\n * }\n * ```\n */\nexport async function withLocale<T>(\n locale: string,\n fn: () => T | Promise<T>,\n serverModule?: { setLocale: (l: string) => void; getI18n: () => Promise<unknown> },\n): Promise<T> {\n if (!serverModule) {\n throw new Error(\n '[fluenti] withLocale requires a server module reference. ' +\n 'Pass the generated server module as the third argument: ' +\n 'withLocale(\"ja\", fn, serverI18n)',\n )\n }\n\n // Read current locale from the module's request store\n // We need to save/restore state. Since we can't access the store directly,\n // we use setLocale to switch and rely on the caller to restore.\n const prevInstance = await serverModule.getI18n()\n const prevLocale = (prevInstance as { locale: string }).locale\n\n try {\n serverModule.setLocale(locale)\n // Force instance recreation by calling getI18n again\n await serverModule.getI18n()\n return await fn()\n } finally {\n serverModule.setLocale(prevLocale)\n await serverModule.getI18n()\n }\n}\n"],"mappings":";AAuBA,eAAsB,EACpB,GACA,GACA,GACY;AACZ,KAAI,CAAC,EACH,OAAU,MACR,sJAGD;CAOH,IAAM,KADe,MAAM,EAAa,SAAS,EACO;AAExD,KAAI;AAIF,SAHA,EAAa,UAAU,EAAO,EAE9B,MAAM,EAAa,SAAS,EACrB,MAAM,GAAI;WACT;AAER,EADA,EAAa,UAAU,EAAW,EAClC,MAAM,EAAa,SAAS"}
1
+ {"version":3,"file":"server.js","names":[],"sources":["../src/with-locale.ts"],"sourcesContent":["/**\n * Per-component locale isolation for RSC.\n *\n * Temporarily switches the request-scoped locale, executes a function,\n * then restores the previous locale. This allows rendering a subtree in\n * a different locale without affecting the rest of the page.\n *\n * @param locale - The locale to switch to for the duration of `fn`.\n * @param fn - The function to execute with the switched locale.\n * @param serverModule - The generated server module reference (auto-injected\n * by the webpack loader in production; only needed when calling manually).\n * @returns The return value of `fn`.\n *\n * @example Basic usage in a Server Component\n * ```tsx\n * // app/page.tsx (Server Component)\n * import { withLocale } from '@fluenti/next/server'\n *\n * export default async function Page() {\n * return (\n * <div>\n * <h1>{t`Main content`}</h1>\n * {await withLocale('ja', async () => (\n * <JapaneseWidget />\n * ))}\n * <Footer />\n * </div>\n * )\n * }\n * ```\n *\n * @example Rendering a multilingual page section\n * ```tsx\n * import { withLocale } from '@fluenti/next/server'\n *\n * export default async function MultilingualPage() {\n * const englishContent = await withLocale('en', async () => (\n * <p>{t`Welcome`}</p>\n * ))\n * const japaneseContent = await withLocale('ja', async () => (\n * <p>{t`Welcome`}</p>\n * ))\n * return (\n * <div>\n * {englishContent}\n * {japaneseContent}\n * </div>\n * )\n * }\n * ```\n */\nexport async function withLocale<T>(\n locale: string,\n fn: () => T | Promise<T>,\n serverModule?: { setLocale: (l: string) => void; getI18n: () => Promise<unknown> },\n): Promise<T> {\n if (!serverModule) {\n throw new Error(\n '[fluenti] withLocale requires a server module reference. ' +\n 'Pass the generated server module as the third argument: ' +\n 'withLocale(\"ja\", fn, serverI18n)',\n )\n }\n\n // Read current locale from the module's request store\n // We need to save/restore state. Since we can't access the store directly,\n // we use setLocale to switch and rely on the caller to restore.\n const prevInstance = await serverModule.getI18n()\n const prevLocale = (prevInstance as { locale: string }).locale\n\n try {\n serverModule.setLocale(locale)\n // Force instance recreation by calling getI18n again\n await serverModule.getI18n()\n return await fn()\n } finally {\n serverModule.setLocale(prevLocale)\n await serverModule.getI18n()\n }\n}\n"],"mappings":";AAmDA,eAAsB,EACpB,GACA,GACA,GACY;AACZ,KAAI,CAAC,EACH,OAAU,MACR,sJAGD;CAOH,IAAM,KADe,MAAM,EAAa,SAAS,EACO;AAExD,KAAI;AAIF,SAHA,EAAa,UAAU,EAAO,EAE9B,MAAM,EAAa,SAAS,EACrB,MAAM,GAAI;WACT;AAER,EADA,EAAa,UAAU,EAAW,EAClC,MAAM,EAAa,SAAS"}
@@ -2,10 +2,18 @@
2
2
  * Per-component locale isolation for RSC.
3
3
  *
4
4
  * Temporarily switches the request-scoped locale, executes a function,
5
- * then restores the previous locale.
5
+ * then restores the previous locale. This allows rendering a subtree in
6
+ * a different locale without affecting the rest of the page.
6
7
  *
7
- * @example
8
+ * @param locale - The locale to switch to for the duration of `fn`.
9
+ * @param fn - The function to execute with the switched locale.
10
+ * @param serverModule - The generated server module reference (auto-injected
11
+ * by the webpack loader in production; only needed when calling manually).
12
+ * @returns The return value of `fn`.
13
+ *
14
+ * @example Basic usage in a Server Component
8
15
  * ```tsx
16
+ * // app/page.tsx (Server Component)
9
17
  * import { withLocale } from '@fluenti/next/server'
10
18
  *
11
19
  * export default async function Page() {
@@ -20,6 +28,26 @@
20
28
  * )
21
29
  * }
22
30
  * ```
31
+ *
32
+ * @example Rendering a multilingual page section
33
+ * ```tsx
34
+ * import { withLocale } from '@fluenti/next/server'
35
+ *
36
+ * export default async function MultilingualPage() {
37
+ * const englishContent = await withLocale('en', async () => (
38
+ * <p>{t`Welcome`}</p>
39
+ * ))
40
+ * const japaneseContent = await withLocale('ja', async () => (
41
+ * <p>{t`Welcome`}</p>
42
+ * ))
43
+ * return (
44
+ * <div>
45
+ * {englishContent}
46
+ * {japaneseContent}
47
+ * </div>
48
+ * )
49
+ * }
50
+ * ```
23
51
  */
24
52
  export declare function withLocale<T>(locale: string, fn: () => T | Promise<T>, serverModule?: {
25
53
  setLocale: (l: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"with-locale.d.ts","sourceRoot":"","sources":["../src/with-locale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EACxB,YAAY,CAAC,EAAE;IAAE,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,GACjF,OAAO,CAAC,CAAC,CAAC,CAwBZ"}
1
+ {"version":3,"file":"with-locale.d.ts","sourceRoot":"","sources":["../src/with-locale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EACxB,YAAY,CAAC,EAAE;IAAE,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,GACjF,OAAO,CAAC,CAAC,CAAC,CAwBZ"}
package/llms-full.txt CHANGED
@@ -50,7 +50,8 @@ In a `withFluenti()` project, the recommended authoring surface is:
50
50
 
51
51
  ```tsx
52
52
  // ✅ Preferred: compile-time authoring surface
53
- import { t, Trans, Plural, Select, DateTime, NumberFormat } from '@fluenti/react'
53
+ import { t } from '@fluenti/react'
54
+ import { Trans, Plural, Select, DateTime, NumberFormat } from '@fluenti/react/components'
54
55
  ```
55
56
 
56
57
  This applies to both client and server authoring.
package/llms.txt CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  `@fluenti/next` wires Fluenti into Next.js. In a `withFluenti()` project, the recommended authoring path is:
6
6
 
7
- - client and server authoring: `import { t, Trans, Plural, Select, DateTime, NumberFormat } from '@fluenti/react'`
7
+ - client and server authoring: `import { t } from '@fluenti/react'` + `import { Trans, Plural, Select, DateTime, NumberFormat } from '@fluenti/react/components'`
8
8
  - Next runtime/integration: `I18nProvider`, `getI18n()`, and `setLocale()` from `@fluenti/next`
9
9
 
10
10
  ## Install
@@ -49,6 +49,43 @@ Provider entry (`@fluenti/next/provider`):
49
49
 
50
50
  ❌ AVOID: `t('some.key')` as the default — this is a legacy i18n pattern.
51
51
 
52
+ ## Code Examples — Next.js
53
+
54
+ ### Client Components
55
+
56
+ ```tsx
57
+ 'use client'
58
+ import { useI18n } from '@fluenti/react'
59
+
60
+ export function Header() {
61
+ const { t } = useI18n()
62
+ return <h1>{t\`Welcome to our app\`}</h1>
63
+ }
64
+ ```
65
+
66
+ ### Server Components (RSC)
67
+
68
+ ```tsx
69
+ import { getI18n } from '@fluenti/next/server'
70
+
71
+ export default async function Page() {
72
+ const { t } = await getI18n()
73
+ return <h1>{t\`Welcome to our app\`}</h1>
74
+ }
75
+ ```
76
+
77
+ ### What NOT to write
78
+
79
+ ❌ Do not use manual key strings:
80
+ ```tsx
81
+ // WRONG
82
+ const { t } = useI18n()
83
+ return <h1>{t('welcome_key')}</h1>
84
+
85
+ // CORRECT
86
+ return <h1>{t\`Welcome to our app\`}</h1>
87
+ ```
88
+
52
89
  ## Docs
53
90
 
54
91
  - Full docs: https://fluenti.dev
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluenti/next",
3
- "version": "0.3.4",
3
+ "version": "0.4.0-rc.0",
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.3.4",
111
- "@fluenti/react": "0.3.4"
110
+ "@fluenti/core": "0.4.0-rc.0",
111
+ "@fluenti/react": "0.4.0-rc.0"
112
112
  },
113
113
  "devDependencies": {
114
114
  "@types/node": "^25",