@fluenti/next 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"loader.cjs","names":[],"sources":["../src/loader.ts"],"sourcesContent":["/**\n * Webpack loader for t`` and t() transforms in Next.js.\n *\n * Runs as enforce: 'pre' to transform source before other loaders.\n * Only statically provable `t` bindings are optimized; runtime `t()` calls\n * continue to work without injected proxy globals.\n */\nimport { scopeTransform } from './scope-transform'\nimport { transformTransComponents } from './trans-transform'\n\n/**\n * Webpack loader function.\n * `this` is the webpack LoaderContext.\n */\nexport default function fluentLoader(this: LoaderContext, source: string): string {\n // Only process .tsx, .ts, .jsx, .js files\n if (!/\\.[jt]sx?$/.test(this.resourcePath)) {\n return source\n }\n\n // Skip node_modules and .next directory\n if (/node_modules|\\.next/.test(this.resourcePath)) {\n return source\n }\n\n let result = source\n const isClientModule = /^\\s*['\"]use client['\"]/.test(result)\n\n // ── <Trans> compile-time optimization (JSX/TSX only) ──────────────\n if (/\\.[jt]sx$/.test(this.resourcePath) && /<Trans[\\s>]/.test(result)) {\n const transResult = transformTransComponents(result)\n if (transResult.transformed) {\n result = transResult.code\n }\n }\n\n // Quick check: does this file contain any Fluenti authoring/runtime surface?\n if (!isServerFluentiFile(result, isClientModule) && !hasFluentPatterns(result)) {\n return result\n }\n\n // Try scope-aware transform first (AST-based, zero false positives)\n try {\n const scoped = scopeTransform(result, {\n framework: 'react',\n serverModuleImport: '@fluenti/next',\n treatFrameworkDirectImportsAsServer: !isClientModule,\n rerouteServerAuthoringImports: !isClientModule,\n errorOnServerUseI18n: !isClientModule,\n })\n if (scoped.transformed) {\n return scoped.code\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n throw new Error(`[fluenti] Transform failed in ${this.resourcePath}: ${msg}`)\n }\n\n return result\n}\n\n/**\n * Quick regex check to avoid full parsing on files without t`` or t().\n */\nfunction hasFluentPatterns(code: string): boolean {\n if (/(?<![.\\w$])t\\(\\s*['\"]/.test(code) || /[A-Za-z_$][\\w$]*\\(\\s*\\{/.test(code)) {\n return true\n }\n\n if (/[A-Za-z_$][\\w$]*`/.test(code) && (code.includes('useI18n') || code.includes('getI18n'))) {\n return true\n }\n\n return /import\\s*\\{\\s*t(?:\\s+as\\s+[A-Za-z_$][\\w$]*)?[\\s,}]/.test(code)\n && (code.includes('@fluenti/react') || code.includes('@fluenti/next'))\n}\n\nfunction isServerFluentiFile(code: string, isClientModule: boolean): boolean {\n if (isClientModule) return false\n if (!code.includes('@fluenti/react') && !code.includes('@fluenti/next')) {\n return false\n }\n\n return /\\b(useI18n|Trans|Plural|Select|DateTime|NumberFormat|t)\\b/.test(code)\n}\n\ninterface LoaderContext {\n resourcePath: string\n getOptions(): Record<string, unknown>\n}\n"],"mappings":"wCAcA,SAAwB,EAAkC,EAAwB,CAOhF,GALI,CAAC,aAAa,KAAK,KAAK,aAAa,EAKrC,sBAAsB,KAAK,KAAK,aAAa,CAC/C,OAAO,EAGT,IAAI,EAAS,EACP,EAAiB,yBAAyB,KAAK,EAAO,CAG5D,GAAI,YAAY,KAAK,KAAK,aAAa,EAAI,cAAc,KAAK,EAAO,CAAE,CACrE,IAAM,GAAA,EAAA,EAAA,0BAAuC,EAAO,CAChD,EAAY,cACd,EAAS,EAAY,MAKzB,GAAI,CAAC,EAAoB,EAAQ,EAAe,EAAI,CAAC,EAAkB,EAAO,CAC5E,OAAO,EAIT,GAAI,CACF,IAAM,GAAA,EAAA,EAAA,gBAAwB,EAAQ,CACpC,UAAW,QACX,mBAAoB,gBACpB,oCAAqC,CAAC,EACtC,8BAA+B,CAAC,EAChC,qBAAsB,CAAC,EACxB,CAAC,CACF,GAAI,EAAO,YACT,OAAO,EAAO,WAET,EAAgB,CACvB,IAAM,EAAM,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAClE,MAAU,MAAM,iCAAiC,KAAK,aAAa,IAAI,IAAM,CAG/E,OAAO,EAMT,SAAS,EAAkB,EAAuB,CAShD,MARI,wBAAwB,KAAK,EAAK,EAAI,0BAA0B,KAAK,EAAK,EAI1E,oBAAoB,KAAK,EAAK,GAAK,EAAK,SAAS,UAAU,EAAI,EAAK,SAAS,UAAU,EAClF,GAGF,qDAAqD,KAAK,EAAK,GAChE,EAAK,SAAS,iBAAiB,EAAI,EAAK,SAAS,gBAAgB,EAGzE,SAAS,EAAoB,EAAc,EAAkC,CAM3E,OALI,GACA,CAAC,EAAK,SAAS,iBAAiB,EAAI,CAAC,EAAK,SAAS,gBAAgB,CAC9D,GAGF,4DAA4D,KAAK,EAAK"}
1
+ {"version":3,"file":"loader.cjs","names":[],"sources":["../src/loader.ts"],"sourcesContent":["/**\n * Webpack loader for t`` and t() transforms in Next.js.\n *\n * Runs as enforce: 'pre' to transform source before other loaders.\n * Only statically provable `t` bindings are optimized; runtime `t()` calls\n * continue to work without injected proxy globals.\n */\nimport { createTransformPipeline, hasScopeTransformCandidate } from '@fluenti/core/transform'\n\nconst pipeline = createTransformPipeline({ framework: 'react' })\n\n/**\n * Webpack loader function.\n * `this` is the webpack LoaderContext.\n */\nexport default function fluentLoader(this: LoaderContext, source: string): string {\n // Only process .tsx, .ts, .jsx, .js files\n if (!/\\.[jt]sx?$/.test(this.resourcePath)) {\n return source\n }\n\n // Skip node_modules and .next directory\n if (/node_modules|\\.next/.test(this.resourcePath)) {\n return source\n }\n\n let result = source\n const isClientModule = /^\\s*['\"]use client['\"]/.test(result)\n\n // ── <Trans> compile-time optimization (JSX/TSX only) ──────────────\n if (/\\.[jt]sx$/.test(this.resourcePath) && /<Trans[\\s>]/.test(result)) {\n const transResult = pipeline.transformTrans(result)\n if (transResult.transformed) {\n result = transResult.code\n }\n }\n\n // Quick check: does this file contain any Fluenti patterns?\n if (!hasScopeTransformCandidate(result)) {\n return result\n }\n\n // Try scope-aware transform (AST-based, zero false positives)\n try {\n const scoped = pipeline.transformScope(result, {\n serverModuleImport: '@fluenti/next',\n treatFrameworkDirectImportsAsServer: !isClientModule,\n rerouteServerAuthoringImports: !isClientModule,\n errorOnServerUseI18n: !isClientModule,\n })\n if (scoped.transformed) {\n return scoped.code\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n throw new Error(`[fluenti] Transform failed in ${this.resourcePath}: ${msg}`)\n }\n\n return result\n}\n\ninterface LoaderContext {\n resourcePath: string\n getOptions(): Record<string, unknown>\n}\n"],"mappings":"yCASA,IAAM,GAAA,EAAA,EAAA,yBAAmC,CAAE,UAAW,QAAS,CAAC,CAMhE,SAAwB,EAAkC,EAAwB,CAOhF,GALI,CAAC,aAAa,KAAK,KAAK,aAAa,EAKrC,sBAAsB,KAAK,KAAK,aAAa,CAC/C,OAAO,EAGT,IAAI,EAAS,EACP,EAAiB,yBAAyB,KAAK,EAAO,CAG5D,GAAI,YAAY,KAAK,KAAK,aAAa,EAAI,cAAc,KAAK,EAAO,CAAE,CACrE,IAAM,EAAc,EAAS,eAAe,EAAO,CAC/C,EAAY,cACd,EAAS,EAAY,MAKzB,GAAI,EAAA,EAAA,EAAA,4BAA4B,EAAO,CACrC,OAAO,EAIT,GAAI,CACF,IAAM,EAAS,EAAS,eAAe,EAAQ,CAC7C,mBAAoB,gBACpB,oCAAqC,CAAC,EACtC,8BAA+B,CAAC,EAChC,qBAAsB,CAAC,EACxB,CAAC,CACF,GAAI,EAAO,YACT,OAAO,EAAO,WAET,EAAgB,CACvB,IAAM,EAAM,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAClE,MAAU,MAAM,iCAAiC,KAAK,aAAa,IAAI,IAAM,CAG/E,OAAO"}
package/dist/loader.js CHANGED
@@ -1,35 +1,29 @@
1
- import { scopeTransform as e, transformTransComponents as t } from "@fluenti/core/internal";
1
+ import { createTransformPipeline as e, hasScopeTransformCandidate as t } from "@fluenti/core/transform";
2
2
  //#region src/loader.ts
3
- function n(n) {
4
- if (!/\.[jt]sx?$/.test(this.resourcePath) || /node_modules|\.next/.test(this.resourcePath)) return n;
5
- let a = n, o = /^\s*['"]use client['"]/.test(a);
6
- if (/\.[jt]sx$/.test(this.resourcePath) && /<Trans[\s>]/.test(a)) {
7
- let e = t(a);
8
- e.transformed && (a = e.code);
3
+ var n = e({ framework: "react" });
4
+ function r(e) {
5
+ if (!/\.[jt]sx?$/.test(this.resourcePath) || /node_modules|\.next/.test(this.resourcePath)) return e;
6
+ let r = e, i = /^\s*['"]use client['"]/.test(r);
7
+ if (/\.[jt]sx$/.test(this.resourcePath) && /<Trans[\s>]/.test(r)) {
8
+ let e = n.transformTrans(r);
9
+ e.transformed && (r = e.code);
9
10
  }
10
- if (!i(a, o) && !r(a)) return a;
11
+ if (!t(r)) return r;
11
12
  try {
12
- let t = e(a, {
13
- framework: "react",
13
+ let e = n.transformScope(r, {
14
14
  serverModuleImport: "@fluenti/next",
15
- treatFrameworkDirectImportsAsServer: !o,
16
- rerouteServerAuthoringImports: !o,
17
- errorOnServerUseI18n: !o
15
+ treatFrameworkDirectImportsAsServer: !i,
16
+ rerouteServerAuthoringImports: !i,
17
+ errorOnServerUseI18n: !i
18
18
  });
19
- if (t.transformed) return t.code;
19
+ if (e.transformed) return e.code;
20
20
  } catch (e) {
21
21
  let t = e instanceof Error ? e.message : String(e);
22
22
  throw Error(`[fluenti] Transform failed in ${this.resourcePath}: ${t}`);
23
23
  }
24
- return a;
25
- }
26
- function r(e) {
27
- return /(?<![.\w$])t\(\s*['"]/.test(e) || /[A-Za-z_$][\w$]*\(\s*\{/.test(e) || /[A-Za-z_$][\w$]*`/.test(e) && (e.includes("useI18n") || e.includes("getI18n")) ? !0 : /import\s*\{\s*t(?:\s+as\s+[A-Za-z_$][\w$]*)?[\s,}]/.test(e) && (e.includes("@fluenti/react") || e.includes("@fluenti/next"));
28
- }
29
- function i(e, t) {
30
- return t || !e.includes("@fluenti/react") && !e.includes("@fluenti/next") ? !1 : /\b(useI18n|Trans|Plural|Select|DateTime|NumberFormat|t)\b/.test(e);
24
+ return r;
31
25
  }
32
26
  //#endregion
33
- export { n as default };
27
+ export { r as default };
34
28
 
35
29
  //# sourceMappingURL=loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","names":[],"sources":["../src/loader.ts"],"sourcesContent":["/**\n * Webpack loader for t`` and t() transforms in Next.js.\n *\n * Runs as enforce: 'pre' to transform source before other loaders.\n * Only statically provable `t` bindings are optimized; runtime `t()` calls\n * continue to work without injected proxy globals.\n */\nimport { scopeTransform } from './scope-transform'\nimport { transformTransComponents } from './trans-transform'\n\n/**\n * Webpack loader function.\n * `this` is the webpack LoaderContext.\n */\nexport default function fluentLoader(this: LoaderContext, source: string): string {\n // Only process .tsx, .ts, .jsx, .js files\n if (!/\\.[jt]sx?$/.test(this.resourcePath)) {\n return source\n }\n\n // Skip node_modules and .next directory\n if (/node_modules|\\.next/.test(this.resourcePath)) {\n return source\n }\n\n let result = source\n const isClientModule = /^\\s*['\"]use client['\"]/.test(result)\n\n // ── <Trans> compile-time optimization (JSX/TSX only) ──────────────\n if (/\\.[jt]sx$/.test(this.resourcePath) && /<Trans[\\s>]/.test(result)) {\n const transResult = transformTransComponents(result)\n if (transResult.transformed) {\n result = transResult.code\n }\n }\n\n // Quick check: does this file contain any Fluenti authoring/runtime surface?\n if (!isServerFluentiFile(result, isClientModule) && !hasFluentPatterns(result)) {\n return result\n }\n\n // Try scope-aware transform first (AST-based, zero false positives)\n try {\n const scoped = scopeTransform(result, {\n framework: 'react',\n serverModuleImport: '@fluenti/next',\n treatFrameworkDirectImportsAsServer: !isClientModule,\n rerouteServerAuthoringImports: !isClientModule,\n errorOnServerUseI18n: !isClientModule,\n })\n if (scoped.transformed) {\n return scoped.code\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n throw new Error(`[fluenti] Transform failed in ${this.resourcePath}: ${msg}`)\n }\n\n return result\n}\n\n/**\n * Quick regex check to avoid full parsing on files without t`` or t().\n */\nfunction hasFluentPatterns(code: string): boolean {\n if (/(?<![.\\w$])t\\(\\s*['\"]/.test(code) || /[A-Za-z_$][\\w$]*\\(\\s*\\{/.test(code)) {\n return true\n }\n\n if (/[A-Za-z_$][\\w$]*`/.test(code) && (code.includes('useI18n') || code.includes('getI18n'))) {\n return true\n }\n\n return /import\\s*\\{\\s*t(?:\\s+as\\s+[A-Za-z_$][\\w$]*)?[\\s,}]/.test(code)\n && (code.includes('@fluenti/react') || code.includes('@fluenti/next'))\n}\n\nfunction isServerFluentiFile(code: string, isClientModule: boolean): boolean {\n if (isClientModule) return false\n if (!code.includes('@fluenti/react') && !code.includes('@fluenti/next')) {\n return false\n }\n\n return /\\b(useI18n|Trans|Plural|Select|DateTime|NumberFormat|t)\\b/.test(code)\n}\n\ninterface LoaderContext {\n resourcePath: string\n getOptions(): Record<string, unknown>\n}\n"],"mappings":";;AAcA,SAAwB,EAAkC,GAAwB;AAOhF,KALI,CAAC,aAAa,KAAK,KAAK,aAAa,IAKrC,sBAAsB,KAAK,KAAK,aAAa,CAC/C,QAAO;CAGT,IAAI,IAAS,GACP,IAAiB,yBAAyB,KAAK,EAAO;AAG5D,KAAI,YAAY,KAAK,KAAK,aAAa,IAAI,cAAc,KAAK,EAAO,EAAE;EACrE,IAAM,IAAc,EAAyB,EAAO;AACpD,EAAI,EAAY,gBACd,IAAS,EAAY;;AAKzB,KAAI,CAAC,EAAoB,GAAQ,EAAe,IAAI,CAAC,EAAkB,EAAO,CAC5E,QAAO;AAIT,KAAI;EACF,IAAM,IAAS,EAAe,GAAQ;GACpC,WAAW;GACX,oBAAoB;GACpB,qCAAqC,CAAC;GACtC,+BAA+B,CAAC;GAChC,sBAAsB,CAAC;GACxB,CAAC;AACF,MAAI,EAAO,YACT,QAAO,EAAO;UAET,GAAgB;EACvB,IAAM,IAAM,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM;AAClE,QAAU,MAAM,iCAAiC,KAAK,aAAa,IAAI,IAAM;;AAG/E,QAAO;;AAMT,SAAS,EAAkB,GAAuB;AAShD,QARI,wBAAwB,KAAK,EAAK,IAAI,0BAA0B,KAAK,EAAK,IAI1E,oBAAoB,KAAK,EAAK,KAAK,EAAK,SAAS,UAAU,IAAI,EAAK,SAAS,UAAU,IAClF,KAGF,qDAAqD,KAAK,EAAK,KAChE,EAAK,SAAS,iBAAiB,IAAI,EAAK,SAAS,gBAAgB;;AAGzE,SAAS,EAAoB,GAAc,GAAkC;AAM3E,QALI,KACA,CAAC,EAAK,SAAS,iBAAiB,IAAI,CAAC,EAAK,SAAS,gBAAgB,GAC9D,KAGF,4DAA4D,KAAK,EAAK"}
1
+ {"version":3,"file":"loader.js","names":[],"sources":["../src/loader.ts"],"sourcesContent":["/**\n * Webpack loader for t`` and t() transforms in Next.js.\n *\n * Runs as enforce: 'pre' to transform source before other loaders.\n * Only statically provable `t` bindings are optimized; runtime `t()` calls\n * continue to work without injected proxy globals.\n */\nimport { createTransformPipeline, hasScopeTransformCandidate } from '@fluenti/core/transform'\n\nconst pipeline = createTransformPipeline({ framework: 'react' })\n\n/**\n * Webpack loader function.\n * `this` is the webpack LoaderContext.\n */\nexport default function fluentLoader(this: LoaderContext, source: string): string {\n // Only process .tsx, .ts, .jsx, .js files\n if (!/\\.[jt]sx?$/.test(this.resourcePath)) {\n return source\n }\n\n // Skip node_modules and .next directory\n if (/node_modules|\\.next/.test(this.resourcePath)) {\n return source\n }\n\n let result = source\n const isClientModule = /^\\s*['\"]use client['\"]/.test(result)\n\n // ── <Trans> compile-time optimization (JSX/TSX only) ──────────────\n if (/\\.[jt]sx$/.test(this.resourcePath) && /<Trans[\\s>]/.test(result)) {\n const transResult = pipeline.transformTrans(result)\n if (transResult.transformed) {\n result = transResult.code\n }\n }\n\n // Quick check: does this file contain any Fluenti patterns?\n if (!hasScopeTransformCandidate(result)) {\n return result\n }\n\n // Try scope-aware transform (AST-based, zero false positives)\n try {\n const scoped = pipeline.transformScope(result, {\n serverModuleImport: '@fluenti/next',\n treatFrameworkDirectImportsAsServer: !isClientModule,\n rerouteServerAuthoringImports: !isClientModule,\n errorOnServerUseI18n: !isClientModule,\n })\n if (scoped.transformed) {\n return scoped.code\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error)\n throw new Error(`[fluenti] Transform failed in ${this.resourcePath}: ${msg}`)\n }\n\n return result\n}\n\ninterface LoaderContext {\n resourcePath: string\n getOptions(): Record<string, unknown>\n}\n"],"mappings":";;AASA,IAAM,IAAW,EAAwB,EAAE,WAAW,SAAS,CAAC;AAMhE,SAAwB,EAAkC,GAAwB;AAOhF,KALI,CAAC,aAAa,KAAK,KAAK,aAAa,IAKrC,sBAAsB,KAAK,KAAK,aAAa,CAC/C,QAAO;CAGT,IAAI,IAAS,GACP,IAAiB,yBAAyB,KAAK,EAAO;AAG5D,KAAI,YAAY,KAAK,KAAK,aAAa,IAAI,cAAc,KAAK,EAAO,EAAE;EACrE,IAAM,IAAc,EAAS,eAAe,EAAO;AACnD,EAAI,EAAY,gBACd,IAAS,EAAY;;AAKzB,KAAI,CAAC,EAA2B,EAAO,CACrC,QAAO;AAIT,KAAI;EACF,IAAM,IAAS,EAAS,eAAe,GAAQ;GAC7C,oBAAoB;GACpB,qCAAqC,CAAC;GACtC,+BAA+B,CAAC;GAChC,sBAAsB,CAAC;GACxB,CAAC;AACF,MAAI,EAAO,YACT,QAAO,EAAO;UAET,GAAgB;EACvB,IAAM,IAAM,aAAiB,QAAQ,EAAM,UAAU,OAAO,EAAM;AAClE,QAAU,MAAM,iCAAiC,KAAK,aAAa,IAAI,IAAM;;AAG/E,QAAO"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @module @fluenti/next/middleware
3
+ *
4
+ * Built-in i18n middleware for Next.js App Router.
5
+ *
6
+ * Uses `x-fluenti-locale` request header to pass locale from middleware to
7
+ * server components — avoids `Set-Cookie` on every request (CDN-friendly).
8
+ *
9
+ * Cookie is only used to remember user preference (set by LocaleSwitcher).
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // src/middleware.ts
14
+ * import { NextResponse } from 'next/server'
15
+ * import { createI18nMiddleware } from '@fluenti/next/middleware'
16
+ *
17
+ * export default createI18nMiddleware({ NextResponse })
18
+ *
19
+ * export const config = {
20
+ * matcher: ['/((?!_next|api|favicon).*)'],
21
+ * }
22
+ * ```
23
+ *
24
+ * @example Composing with Clerk
25
+ * ```ts
26
+ * import { NextResponse } from 'next/server'
27
+ * import { clerkMiddleware } from '@clerk/nextjs/server'
28
+ * import { createI18nMiddleware } from '@fluenti/next/middleware'
29
+ *
30
+ * const i18nMiddleware = createI18nMiddleware({ NextResponse })
31
+ *
32
+ * export default clerkMiddleware(async (auth, req) => {
33
+ * await auth.protect()
34
+ * return i18nMiddleware(req)
35
+ * })
36
+ * ```
37
+ */
38
+ /** Header name used to pass resolved locale from middleware to RSC */
39
+ export declare const LOCALE_HEADER = "x-fluenti-locale";
40
+ export interface I18nMiddlewareConfig {
41
+ /** Available locales. If omitted, reads from `fluenti.config.ts`. */
42
+ locales?: string[];
43
+ /** Source/default locale. If omitted, reads from `fluenti.config.ts`. */
44
+ sourceLocale?: string;
45
+ /** Cookie name for reading user preference (default: 'locale') */
46
+ cookieName?: string;
47
+ /**
48
+ * Locale prefix strategy:
49
+ * - `'always'`: all locales get a URL prefix (e.g. `/en/about`, `/fr/about`)
50
+ * - `'as-needed'`: source locale has no prefix, others do (e.g. `/about`, `/fr/about`)
51
+ *
52
+ * Default: `'as-needed'`
53
+ */
54
+ localePrefix?: 'always' | 'as-needed';
55
+ }
56
+ type NextRequest = {
57
+ nextUrl: {
58
+ pathname: string;
59
+ search: string;
60
+ };
61
+ url: string;
62
+ cookies: {
63
+ get(name: string): {
64
+ value: string;
65
+ } | undefined;
66
+ };
67
+ headers: Headers;
68
+ };
69
+ type NextResponseStatic = {
70
+ redirect(url: URL): NextResponseInstance;
71
+ rewrite(url: URL, init?: Record<string, unknown>): NextResponseInstance;
72
+ next(init?: Record<string, unknown>): NextResponseInstance;
73
+ };
74
+ type NextResponseInstance = {
75
+ headers: {
76
+ set(name: string, value: string): void;
77
+ };
78
+ };
79
+ /**
80
+ * Create an i18n middleware function for Next.js.
81
+ *
82
+ * Requires `NextResponse` to be passed in because the middleware module runs
83
+ * in Next.js Edge Runtime where `require('next/server')` is not available.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * import { NextResponse } from 'next/server'
88
+ * import { createI18nMiddleware } from '@fluenti/next/middleware'
89
+ *
90
+ * export default createI18nMiddleware({ NextResponse })
91
+ * ```
92
+ *
93
+ * @example With explicit locales
94
+ * ```ts
95
+ * import { NextResponse } from 'next/server'
96
+ * import { createI18nMiddleware } from '@fluenti/next/middleware'
97
+ *
98
+ * export default createI18nMiddleware({
99
+ * NextResponse,
100
+ * locales: ['en', 'ja', 'zh-CN'],
101
+ * sourceLocale: 'en',
102
+ * })
103
+ * ```
104
+ */
105
+ export declare function createI18nMiddleware(config: I18nMiddlewareConfig & {
106
+ NextResponse: NextResponseStatic;
107
+ }): (request: NextRequest) => NextResponseInstance;
108
+ export {};
109
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,sEAAsE;AACtE,eAAO,MAAM,aAAa,qBAAqB,CAAA;AAE/C,MAAM,WAAW,oBAAoB;IACnC,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAA;CACtC;AAED,KAAK,WAAW,GAAG;IACjB,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,GAAG,SAAS,CAAA;KAAE,CAAA;IAC7D,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,oBAAoB,CAAA;IACxC,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,oBAAoB,CAAA;IACvE,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,oBAAoB,CAAA;CAC3D,CAAA;AAED,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CACpD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG;IAAE,YAAY,EAAE,kBAAkB,CAAA;CAAE,IAOvE,SAAS,WAAW,0BA6DpD"}
@@ -0,0 +1,40 @@
1
+ export interface GetLocalePathOptions {
2
+ /** Source/default locale (no prefix in as-needed mode) */
3
+ sourceLocale?: string;
4
+ /**
5
+ * Known locale codes (e.g. ['en', 'fr', 'ja']).
6
+ * When provided, the existing prefix is only stripped when it's an actual locale —
7
+ * preventing false matches on generic 2-letter path segments like /my or /us.
8
+ */
9
+ locales?: string[];
10
+ }
11
+ /**
12
+ * Get the locale-prefixed path for a given pathname and locale.
13
+ *
14
+ * Pure function — works on both server and client.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * getLocalePath('/about', 'fr') // → '/fr/about'
19
+ * getLocalePath('/about', 'en') // → '/about' (source locale, no prefix)
20
+ * getLocalePath('/fr/about', 'en') // → '/about'
21
+ * getLocalePath('/fr/about', 'ja') // → '/ja/about'
22
+ * ```
23
+ */
24
+ export declare function getLocalePath(pathname: string, locale: string, options?: GetLocalePathOptions): string;
25
+ /**
26
+ * Hook for switching locales in Next.js App Router.
27
+ *
28
+ * Sets a cookie to remember user preference, navigates to the new locale path,
29
+ * and triggers a server component refresh.
30
+ */
31
+ export declare function useLocaleSwitcher(options?: {
32
+ /** Override the source/default locale instead of inferring from locales[0]. */
33
+ sourceLocale?: string;
34
+ }): {
35
+ switchLocale: (newLocale: string) => void;
36
+ currentLocale: string;
37
+ locales: string[];
38
+ sourceLocale: string;
39
+ };
40
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,oBAAoB;IACnC,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,oBAAoB,GAC7B,MAAM,CAwBR;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE;IAC1C,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;8BASkC,MAAM;;;;EAkBxC"}
@@ -1,6 +1,8 @@
1
1
  import { WithFluentConfig, ResolvedFluentConfig } from './types';
2
2
  /**
3
3
  * Read fluenti.config.ts and merge with withFluenti() overrides.
4
+ *
5
+ * Delegates config file loading to `@fluenti/core`'s shared `loadConfigSync()`.
4
6
  */
5
7
  export declare function resolveConfig(projectRoot: string, overrides?: WithFluentConfig): ResolvedFluentConfig;
6
8
  //# sourceMappingURL=read-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"read-config.d.ts","sourceRoot":"","sources":["../src/read-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAarE;;GAEG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,gBAAgB,GAC3B,oBAAoB,CAiCtB"}
1
+ {"version":3,"file":"read-config.d.ts","sourceRoot":"","sources":["../src/read-config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAErE;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,gBAAgB,GAC3B,oBAAoB,CAyBtB"}
package/dist/types.d.ts CHANGED
@@ -1,17 +1,13 @@
1
- import { DateFormatOptions, NumberFormatOptions, Locale } from '@fluenti/core';
1
+ import { FluentiBuildConfig } from '@fluenti/core/internal';
2
2
  /**
3
3
  * Configuration for `withFluenti()`.
4
4
  *
5
- * Reads defaults from `fluenti.config.ts` in the project root.
6
- * All options here override the config file values.
5
+ * All i18n config (locales, sourceLocale, splitting, etc.) lives in `fluenti.config.ts`.
6
+ * Only Next.js-specific options are set here.
7
7
  */
8
8
  export interface WithFluentConfig {
9
- /** Override `fluenti.config.ts` locales */
10
- locales?: string[];
11
- /** Override `fluenti.config.ts` sourceLocale (used as defaultLocale) */
12
- defaultLocale?: string;
13
- /** Override `fluenti.config.ts` compileOutDir */
14
- compiledDir?: string;
9
+ /** fluenti.config.ts path or inline config */
10
+ config?: string | FluentiBuildConfig;
15
11
  /** Custom serverModule path (skip auto-generation) */
16
12
  serverModule?: string;
17
13
  /** Where to generate the serverModule (default: node_modules/.fluenti) */
@@ -29,33 +25,24 @@ export interface WithFluentConfig {
29
25
  resolveLocale?: string;
30
26
  /** Cookie name used for locale detection (default: 'locale') */
31
27
  cookieName?: string;
32
- /** Custom date format styles */
33
- dateFormats?: DateFormatOptions;
34
- /** Custom number format styles */
35
- numberFormats?: NumberFormatOptions;
36
- /** Fallback chain per locale */
37
- fallbackChain?: Record<string, Locale[]>;
38
- /** Auto extract+compile in dev mode (default: true) */
39
- devAutoCompile?: boolean;
40
- /** Debounce delay in ms for dev auto-compile (default: 1000). Increase if you see excessive recompilation. */
41
- devAutoCompileDelay?: number;
42
- /** Auto extract+compile before production build (default: true) */
43
- buildAutoCompile?: boolean;
28
+ /**
29
+ * Webpack loader enforce mode (default: 'pre').
30
+ *
31
+ * Set to `undefined` to let webpack determine ordering, or `'post'` to run after other loaders.
32
+ * This can be useful when other loaders need to process files before Fluenti's transform.
33
+ */
34
+ loaderEnforce?: 'pre' | 'post' | undefined;
44
35
  }
45
36
  /**
46
37
  * Resolved config after merging fluenti.config.ts + withFluenti() overrides.
47
38
  */
48
39
  export interface ResolvedFluentConfig {
49
- locales: string[];
50
- defaultLocale: string;
51
- compiledDir: string;
40
+ /** The fully resolved FluentiBuildConfig */
41
+ fluentiConfig: FluentiBuildConfig;
52
42
  serverModule: string | null;
53
43
  serverModuleOutDir: string;
54
44
  resolveLocale?: string;
55
45
  cookieName: string;
56
- dateFormats?: DateFormatOptions;
57
- numberFormats?: NumberFormatOptions;
58
- fallbackChain?: Record<string, Locale[]>;
59
46
  }
60
47
  /**
61
48
  * Props for the I18nProvider server component.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAEnF;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAE3B;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB,gCAAgC;IAChC,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,kCAAkC;IAClC,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,gCAAgC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,uDAAuD;IACvD,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,8GAA8G;IAC9G,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAEhE;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAAA;IAIpC,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAE3B;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAA;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,4CAA4C;IAC5C,aAAa,EAAE,kBAAkB,CAAA;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B"}
package/llms-full.txt ADDED
@@ -0,0 +1,122 @@
1
+ # @fluenti/next
2
+
3
+ > Next.js integration for Fluenti — `withFluenti()`, strict compile-time transforms, and generated server runtime helpers.
4
+
5
+ @fluenti/next integrates Fluenti into Next.js projects. Its job is to:
6
+
7
+ - wrap `next.config` with `withFluenti()`
8
+ - install the Fluenti loader
9
+ - generate the internal server runtime module consumed as `@fluenti/next`
10
+ - keep Next authoring aligned with the main React authoring surface
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pnpm add @fluenti/core @fluenti/react @fluenti/next
16
+ ```
17
+
18
+ ## Main public entry
19
+
20
+ ```ts
21
+ import { withFluenti } from '@fluenti/next'
22
+
23
+ export default withFluenti()({
24
+ reactStrictMode: true,
25
+ })
26
+ ```
27
+
28
+ ### `withFluenti(config?)`
29
+
30
+ Supports:
31
+
32
+ - `withFluenti(fluentiConfig?)(nextConfig)`
33
+ - `withFluenti(nextConfig)`
34
+
35
+ Public config fields:
36
+
37
+ - `locales`
38
+ - `defaultLocale`
39
+ - `compiledDir`
40
+ - `serverModule`
41
+ - `serverModuleOutDir`
42
+ - `resolveLocale`
43
+ - `dateFormats`
44
+ - `numberFormats`
45
+ - `fallbackChain`
46
+
47
+ ## Authoring model in Next
48
+
49
+ In a `withFluenti()` project, the recommended authoring surface is:
50
+
51
+ ```tsx
52
+ // ✅ Preferred: compile-time authoring surface
53
+ import { t, Trans, Plural, Select, DateTime, NumberFormat } from '@fluenti/react'
54
+ ```
55
+
56
+ This applies to both client and server authoring.
57
+
58
+ For runtime and integration concerns, use the generated module:
59
+
60
+ ```ts
61
+ import { I18nProvider, getI18n, setLocale } from '@fluenti/next'
62
+ ```
63
+
64
+ ### Important boundaries
65
+
66
+ - imported `t` is compile-time only — ✅ this is the preferred API
67
+ - client and server authoring should stay on `@fluenti/react`
68
+ - server runtime access should use `await getI18n()` — ⚠️ runtime fallback for dynamic keys
69
+ - direct-import server `t` requires an async server scope
70
+
71
+ ## What `withFluenti()` does
72
+
73
+ 1. resolves Fluenti config from `fluenti.config.ts` and explicit overrides
74
+ 2. generates a server module under the configured output directory
75
+ 3. aliases that generated module as `@fluenti/next`
76
+ 4. runs the loader on app code so official authoring APIs are transformed correctly
77
+
78
+ ## Generated runtime module
79
+
80
+ `@fluenti/next` is not a hand-authored package entry. It is a generated module created by `withFluenti()`.
81
+
82
+ It provides:
83
+
84
+ - `I18nProvider`
85
+ - `setLocale`
86
+ - `getI18n`
87
+ - compile-time `t` stub
88
+ - server-side `Trans`
89
+ - server-side `Plural`
90
+ - server-side `Select`
91
+ - server-side `DateTime`
92
+ - server-side `NumberFormat`
93
+
94
+ ### `I18nProvider`
95
+
96
+ Use in root layouts to initialize server and client runtime state.
97
+
98
+ ### `getI18n()`
99
+
100
+ Use in server components, route handlers, metadata functions, and server actions when you need the full runtime API.
101
+
102
+ ### `setLocale()`
103
+
104
+ Sets the request locale for the generated server runtime.
105
+
106
+ ## Public package subpaths
107
+
108
+ - `@fluenti/next` — config wrapper and types
109
+ - `@fluenti/next/server` — server-side helpers exported by the package itself
110
+ - `@fluenti/next/provider` — provider-related types/helpers
111
+ - `@fluenti/next/client` — client-side type declarations
112
+
113
+ `@fluenti/next` is created per project by `withFluenti()`.
114
+
115
+ ## Relationship to `@fluenti/react`
116
+
117
+ `@fluenti/next` builds on `@fluenti/react`.
118
+
119
+ - authoring surface: `@fluenti/react`
120
+ - Next-specific runtime integration: `@fluenti/next` + generated module
121
+
122
+ This keeps the main mental model aligned across React SPA, Vite, and Next.