@fluenti/next 0.4.0-rc.4 → 0.6.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.
Files changed (80) hide show
  1. package/dist/chunk-DyIg6Wiv.js +7 -0
  2. package/dist/i18n-config.cjs +1 -1
  3. package/dist/i18n-config.cjs.map +1 -1
  4. package/dist/index.cjs +69 -41
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.js +188 -124
  7. package/dist/index.js.map +1 -1
  8. package/dist/loader.cjs +1 -1
  9. package/dist/loader.cjs.map +1 -1
  10. package/dist/loader.js +5 -0
  11. package/dist/loader.js.map +1 -1
  12. package/dist/middleware.cjs +1 -1
  13. package/dist/middleware.cjs.map +1 -1
  14. package/dist/middleware.js +66 -71
  15. package/dist/middleware.js.map +1 -1
  16. package/dist/navigation.cjs +1 -1
  17. package/dist/navigation.cjs.map +1 -1
  18. package/dist/navigation.js +61 -63
  19. package/dist/navigation.js.map +1 -1
  20. package/dist/{client-provider.d.ts → packages/next-plugin/src/client-provider.d.ts} +1 -1
  21. package/dist/packages/next-plugin/src/client-provider.d.ts.map +1 -0
  22. package/dist/packages/next-plugin/src/create-navigation.d.ts.map +1 -0
  23. package/dist/packages/next-plugin/src/dev-runner.d.ts.map +1 -0
  24. package/dist/packages/next-plugin/src/dev-watcher.d.ts.map +1 -0
  25. package/dist/packages/next-plugin/src/generate-server-module.d.ts.map +1 -0
  26. package/dist/packages/next-plugin/src/i18n-config.d.ts.map +1 -0
  27. package/dist/{index.d.ts → packages/next-plugin/src/index.d.ts} +32 -29
  28. package/dist/packages/next-plugin/src/index.d.ts.map +1 -0
  29. package/dist/packages/next-plugin/src/middleware.d.ts.map +1 -0
  30. package/dist/{navigation.d.ts → packages/next-plugin/src/navigation.d.ts} +2 -2
  31. package/dist/packages/next-plugin/src/navigation.d.ts.map +1 -0
  32. package/dist/packages/next-plugin/src/provider.d.ts.map +1 -0
  33. package/dist/packages/next-plugin/src/read-config.d.ts.map +1 -0
  34. package/dist/packages/next-plugin/src/routing.d.ts.map +1 -0
  35. package/dist/packages/next-plugin/src/server.d.ts.map +1 -0
  36. package/dist/{types.d.ts → packages/next-plugin/src/types.d.ts} +11 -1
  37. package/dist/packages/next-plugin/src/types.d.ts.map +1 -0
  38. package/dist/packages/next-plugin/src/with-fluenti.d.ts.map +1 -0
  39. package/dist/packages/next-plugin/src/with-locale.d.ts.map +1 -0
  40. package/dist/provider.cjs +1 -1
  41. package/dist/provider.cjs.map +1 -1
  42. package/dist/routing-CRln5Gw7.js +65 -0
  43. package/dist/routing-CRln5Gw7.js.map +1 -0
  44. package/dist/routing-D4p8HvMH.cjs +2 -0
  45. package/dist/routing-D4p8HvMH.cjs.map +1 -0
  46. package/dist/server.cjs +1 -1
  47. package/dist/server.cjs.map +1 -1
  48. package/package.json +16 -16
  49. package/dist/client-provider.d.ts.map +0 -1
  50. package/dist/create-navigation.d.ts.map +0 -1
  51. package/dist/dev-runner.d.ts.map +0 -1
  52. package/dist/dev-watcher.d.ts.map +0 -1
  53. package/dist/generate-server-module.d.ts.map +0 -1
  54. package/dist/i18n-config.d.ts.map +0 -1
  55. package/dist/index.d.ts.map +0 -1
  56. package/dist/middleware.d.ts.map +0 -1
  57. package/dist/navigation.d.ts.map +0 -1
  58. package/dist/provider.d.ts.map +0 -1
  59. package/dist/read-config.d.ts.map +0 -1
  60. package/dist/routing-DBVu_7tk.js +0 -72
  61. package/dist/routing-DBVu_7tk.js.map +0 -1
  62. package/dist/routing-Dy57-Uao.cjs +0 -2
  63. package/dist/routing-Dy57-Uao.cjs.map +0 -1
  64. package/dist/routing.d.ts.map +0 -1
  65. package/dist/server.d.ts.map +0 -1
  66. package/dist/types.d.ts.map +0 -1
  67. package/dist/with-fluenti.d.ts.map +0 -1
  68. package/dist/with-locale.d.ts.map +0 -1
  69. /package/dist/{create-navigation.d.ts → packages/next-plugin/src/create-navigation.d.ts} +0 -0
  70. /package/dist/{dev-runner.d.ts → packages/next-plugin/src/dev-runner.d.ts} +0 -0
  71. /package/dist/{dev-watcher.d.ts → packages/next-plugin/src/dev-watcher.d.ts} +0 -0
  72. /package/dist/{generate-server-module.d.ts → packages/next-plugin/src/generate-server-module.d.ts} +0 -0
  73. /package/dist/{i18n-config.d.ts → packages/next-plugin/src/i18n-config.d.ts} +0 -0
  74. /package/dist/{middleware.d.ts → packages/next-plugin/src/middleware.d.ts} +0 -0
  75. /package/dist/{provider.d.ts → packages/next-plugin/src/provider.d.ts} +0 -0
  76. /package/dist/{read-config.d.ts → packages/next-plugin/src/read-config.d.ts} +0 -0
  77. /package/dist/{routing.d.ts → packages/next-plugin/src/routing.d.ts} +0 -0
  78. /package/dist/{server.d.ts → packages/next-plugin/src/server.d.ts} +0 -0
  79. /package/dist/{with-fluenti.d.ts → packages/next-plugin/src/with-fluenti.d.ts} +0 -0
  80. /package/dist/{with-locale.d.ts → packages/next-plugin/src/with-locale.d.ts} +0 -0
@@ -1,109 +1,108 @@
1
- import { i as e, n as t, r as n } from "./routing-DBVu_7tk.js";
2
- import { cookieName as r, locales as i, sourceLocale as a } from "@fluenti/next/i18n-config";
1
+ import { n as e, r as t } from "./routing-CRln5Gw7.js";
2
+ import { cookieName as n, locales as r, sourceLocale as i } from "@fluenti/next/i18n-config";
3
3
  //#region src/middleware.ts
4
- t();
5
- var o = "x-fluenti-locale";
6
- function s(t) {
7
- let { NextResponse: s } = t, p = t.locales ?? i, g = t.sourceLocale ?? a, _ = t.cookieName ?? r, v = t.localePrefix ?? "as-needed", y = t.rewriteDefaultLocale ?? !1, b = t.setCookie ?? !1, x = t.cookieOptions ?? {}, S = t.localeDetection ?? !0, C = t.alternateLinks ?? !1, w = t.pathnames, T = t.domains, E = t.beforeResponse;
8
- function D(e, n, r, i, a) {
9
- if (e.headers.set(o, r), b && u(e, n, r, _, a, x), C || t.getAlternateLinks) {
10
- let i = t.getAlternateLinks ? h(t.getAlternateLinks, n, r, p) : m(n, p, g, v, n.nextUrl.basePath ?? "", w);
11
- e.headers.set("Link", i);
4
+ var a = "x-fluenti-locale";
5
+ function o(o) {
6
+ let { NextResponse: f } = o, h = o.locales ?? r, g = o.sourceLocale ?? i, _ = o.cookieName ?? n, v = o.localePrefix ?? "as-needed", y = o.rewriteDefaultLocale ?? !1, b = o.setCookie ?? !1, x = o.cookieOptions ?? {}, S = o.localeDetection ?? !0, C = o.alternateLinks ?? !1, w = o.pathnames, T = o.domains, E = o.beforeResponse;
7
+ function D(e, t, n, r, i) {
8
+ if (e.headers.set(a, n), b && l(e, t, n, _, i, x), C || o.getAlternateLinks) {
9
+ let r = o.getAlternateLinks ? m(o.getAlternateLinks, t, n, h) : p(t, h, g, v, t.nextUrl.basePath ?? "", w);
10
+ e.headers.set("Link", r);
12
11
  }
13
12
  if (E) {
14
- let t = E({
13
+ let i = E({
15
14
  response: e,
16
- request: n,
17
- locale: r,
18
- type: i
15
+ request: t,
16
+ locale: n,
17
+ type: r
19
18
  });
20
- if (t) return t;
19
+ if (i) return i;
21
20
  }
22
21
  return e;
23
22
  }
24
- return function(r) {
25
- let i = p, a = g, { pathname: u, search: m } = r.nextUrl, h = r.nextUrl.basePath ?? "", b = u.split("/"), x = b[1] ?? "", C = v === "never" ? null : c(x, i), E;
23
+ return function(n) {
24
+ let r = h, i = g, { pathname: l, search: p } = n.nextUrl, m = n.nextUrl.basePath ?? "", b = l.split("/"), x = b[1] ?? "", C = v === "never" ? null : s(x, r), E;
26
25
  if (C) E = C;
27
- else if (!S) E = a;
26
+ else if (!S) E = i;
28
27
  else {
29
- let e = t.detectLocale?.(r);
30
- E = e !== void 0 && c(e, i) !== null ? c(e, i) : T ? d(r, T, i) ?? f(r, i, a, _) : f(r, i, a, _);
28
+ let e = o.detectLocale?.(n);
29
+ E = e !== void 0 && s(e, r) !== null ? s(e, r) : T ? u(n, T, r) ?? d(n, r, i, _) : d(n, r, i, _);
31
30
  }
32
- let O = new Headers(r.headers);
33
- if (O.set(o, E), v === "never") {
31
+ let O = new Headers(n.headers);
32
+ if (O.set(a, E), v === "never") {
34
33
  if (y) {
35
- let e = new URL(`${h}/${E}${u}${m}`, r.url);
36
- return D(s.rewrite(e, { request: { headers: O } }), r, E, "rewrite", null);
34
+ let e = new URL(`${m}/${E}${l}${p}`, n.url);
35
+ return D(f.rewrite(e, { request: { headers: O } }), n, E, "rewrite", null);
37
36
  }
38
- return D(s.next({ request: { headers: O } }), r, E, "next", null);
37
+ return D(f.next({ request: { headers: O } }), n, E, "next", null);
39
38
  }
40
39
  if (w && C) {
41
- let t = l("/" + b.slice(2).join("/")), i = n(t, E, w);
40
+ let r = c("/" + b.slice(2).join("/")), i = e(r, E, w);
42
41
  if (i) {
43
- let e = new URL(`${h}/${E}${i}${m}`, r.url);
44
- return D(s.rewrite(e, { request: { headers: O } }), r, E, "rewrite", C);
42
+ let e = new URL(`${m}/${E}${i}${p}`, n.url);
43
+ return D(f.rewrite(e, { request: { headers: O } }), n, E, "rewrite", C);
45
44
  }
46
- let a = e(t, E, w);
47
- if (a && a !== t) {
48
- let e = new URL(`${h}/${E}${a}${m}`, r.url);
49
- return D(s.redirect(e), r, E, "redirect", C);
45
+ let a = t(r, E, w);
46
+ if (a && a !== r) {
47
+ let e = new URL(`${m}/${E}${a}${p}`, n.url);
48
+ return D(f.redirect(e), n, E, "redirect", C);
50
49
  }
51
50
  }
52
- if (!C && (v === "always" || E !== a)) {
53
- let t = u;
51
+ if (!C && (v === "always" || E !== i)) {
52
+ let e = l;
54
53
  if (w) {
55
- let n = e(u, E, w);
56
- n && (t = n);
54
+ let n = t(l, E, w);
55
+ n && (e = n);
57
56
  }
58
- let n = new URL(`${h}/${E}${t}${m}`, r.url);
59
- return D(s.redirect(n), r, E, "redirect", C);
57
+ let r = new URL(`${m}/${E}${e}${p}`, n.url);
58
+ return D(f.redirect(r), n, E, "redirect", C);
60
59
  }
61
- if (!C && E === a && y) {
62
- let e = new URL(`${h}/${a}${u}${m}`, r.url);
63
- return D(s.rewrite(e, { request: { headers: O } }), r, E, "rewrite", C);
60
+ if (!C && E === i && y) {
61
+ let e = new URL(`${m}/${i}${l}${p}`, n.url);
62
+ return D(f.rewrite(e, { request: { headers: O } }), n, E, "rewrite", C);
64
63
  }
65
- if (v === "as-needed" && C === a) {
66
- let e = l("/" + b.slice(2).join("/"));
64
+ if (v === "as-needed" && C === i) {
65
+ let e = c("/" + b.slice(2).join("/"));
67
66
  if (y) {
68
- let t = new URL(`${h}${e}${m}`, r.url);
69
- return D(s.redirect(t), r, E, "redirect", C);
67
+ let t = new URL(`${m}${e}${p}`, n.url);
68
+ return D(f.redirect(t), n, E, "redirect", C);
70
69
  }
71
- let t = new URL(`${h}${e}${m}`, r.url);
72
- return D(s.rewrite(t, { request: { headers: O } }), r, E, "rewrite", C);
70
+ let t = new URL(`${m}${e}${p}`, n.url);
71
+ return D(f.rewrite(t, { request: { headers: O } }), n, E, "rewrite", C);
73
72
  }
74
- return D(s.next({ request: { headers: O } }), r, E, "next", C);
73
+ return D(f.next({ request: { headers: O } }), n, E, "next", C);
75
74
  };
76
75
  }
77
- function c(e, t) {
76
+ function s(e, t) {
78
77
  if (!e) return null;
79
78
  let n = e.toLowerCase();
80
79
  return t.find((e) => e.toLowerCase() === n) ?? null;
81
80
  }
82
- function l(e) {
81
+ function c(e) {
83
82
  return e.replace(/\/+/g, "/") || "/";
84
83
  }
85
- function u(e, t, n, r, i, a) {
84
+ function l(e, t, n, r, i, a) {
86
85
  if (i || t.cookies.get(r)?.value === n) return;
87
86
  let o = [`${r}=${encodeURIComponent(n)}`];
88
87
  o.push(`path=${a.path ?? "/"}`), o.push(`max-age=${a.maxAge ?? 31536e3}`), o.push(`samesite=${a.sameSite ?? "lax"}`), a.domain && o.push(`domain=${a.domain}`), (a.secure ?? t.url.startsWith("https")) && o.push("secure"), e.headers.set("set-cookie", o.join(";"));
89
88
  }
90
- function d(e, t, n) {
89
+ function u(e, t, n) {
91
90
  let r = e.headers.get("host")?.split(":")[0] ?? "";
92
91
  for (let e of t) if (r === e.domain || r.endsWith("." + e.domain)) {
93
- let t = c(e.defaultLocale, e.locales ?? n);
92
+ let t = s(e.defaultLocale, e.locales ?? n);
94
93
  if (t) return t;
95
94
  }
96
95
  return null;
97
96
  }
98
- function f(e, t, n, r) {
97
+ function d(e, t, n, r) {
99
98
  let i = e.cookies.get(r)?.value;
100
99
  if (i) {
101
- let e = c(i, t);
100
+ let e = s(i, t);
102
101
  if (e) return e;
103
102
  }
104
103
  let a = e.headers.get("accept-language");
105
104
  if (a) for (let e of a.split(",")) {
106
- let n = e.split(";")[0].trim(), r = c(n, t);
105
+ let n = e.split(";")[0].trim(), r = s(n, t);
107
106
  if (r) return r;
108
107
  let i = n.split("-")[0].toLowerCase(), a = t.find((e) => {
109
108
  let t = e.toLowerCase();
@@ -113,25 +112,21 @@ function f(e, t, n, r) {
113
112
  }
114
113
  return n;
115
114
  }
116
- function p(e, t) {
115
+ function f(e, t) {
117
116
  let n = e.split("/");
118
- return c(n[1] ?? "", t) ? l("/" + n.slice(2).join("/")) : e;
117
+ return s(n[1] ?? "", t) ? c("/" + n.slice(2).join("/")) : e;
119
118
  }
120
- function m(t, n, r, i, a, o) {
121
- let s = new URL(t.url).origin, c = p(t.nextUrl.pathname, n), l = n.map((t) => {
122
- let n;
123
- if (n = i === "never" || i === "as-needed" && t === r ? c : `/${t}${c}`, o) {
124
- let a = e(c, t, o);
125
- a && (n = i === "never" || i === "as-needed" && t === r ? a : `/${t}${a}`);
126
- }
127
- return `<${s}${a}${n}>; rel="alternate"; hreflang="${t}"`;
128
- }), u = i === "always" ? `/${r}${c}` : c;
129
- return l.push(`<${s}${a}${u}>; rel="alternate"; hreflang="x-default"`), l.join(", ");
119
+ function p(n, r, i, a, o, c) {
120
+ let l = new URL(n.url).origin, u = f(n.nextUrl.pathname, r), d = n.nextUrl.pathname.split("/")[1] ?? "", p = a === "never" ? i : s(d, r) ?? i, m = c ? e(u, p, c) ?? u : u, h = r.map((e) => {
121
+ let n = c ? t(m, e, c) ?? m : m, r;
122
+ return r = a === "never" || a === "as-needed" && e === i ? n : `/${e}${n}`, `<${l}${o}${r}>; rel="alternate"; hreflang="${e}"`;
123
+ }), g = c ? t(m, i, c) ?? m : m, _ = a === "always" ? `/${i}${g}` : g;
124
+ return h.push(`<${l}${o}${_}>; rel="alternate"; hreflang="x-default"`), h.join(", ");
130
125
  }
131
- function h(e, t, n, r) {
126
+ function m(e, t, n, r) {
132
127
  let i = new URL(t.url).origin;
133
128
  return e({
134
- pathname: p(t.nextUrl.pathname, r),
129
+ pathname: f(t.nextUrl.pathname, r),
135
130
  locale: n,
136
131
  locales: r,
137
132
  origin: i,
@@ -139,6 +134,6 @@ function h(e, t, n, r) {
139
134
  }).map((e) => `<${e.href}>; rel="alternate"; hreflang="${e.hreflang}"`).join(", ");
140
135
  }
141
136
  //#endregion
142
- export { o as LOCALE_HEADER, s as createI18nMiddleware };
137
+ export { a as LOCALE_HEADER, o as createI18nMiddleware };
143
138
 
144
139
  //# sourceMappingURL=middleware.js.map
@@ -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 * @example Minimal\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({ NextResponse })\n * ```\n *\n * @example With pathnames + alternateLinks + domains\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({\n * NextResponse,\n * rewriteDefaultLocale: true,\n * alternateLinks: true,\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * },\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * ],\n * })\n * ```\n */\n\nimport {\n locales as _configLocales,\n sourceLocale as _configSourceLocale,\n cookieName as _configCookieName,\n} from '@fluenti/next/i18n-config'\n\n/** Header name used to pass resolved locale from middleware to RSC */\nexport const LOCALE_HEADER = 'x-fluenti-locale'\n\nexport interface CookieOptions {\n /** Cookie domain (e.g. '.example.com' for cross-subdomain) */\n domain?: string\n /** Secure flag (default: auto-detect from request URL) */\n secure?: boolean\n /** SameSite attribute (default: 'lax') */\n sameSite?: 'lax' | 'strict' | 'none'\n /** Max age in seconds (default: 31536000 = 1 year) */\n maxAge?: number\n /** Cookie path (default: '/') */\n path?: string\n}\n\nexport interface DomainConfig {\n /** Domain hostname (e.g. 'fr.example.com') */\n domain: string\n /** Default locale for this domain */\n defaultLocale: string\n /** Optional subset of locales available on this domain */\n locales?: string[]\n}\n\nexport interface AlternateLinkEntry {\n href: string\n hreflang: string\n}\n\nexport interface I18nMiddlewareConfig {\n /** Available locales. If omitted, reads from `fluenti.config.ts`. */\n locales?: string[]\n /** Source/default locale. If omitted, reads from `fluenti.config.ts`. */\n sourceLocale?: string\n /** Cookie name for reading user preference (default: 'locale') */\n cookieName?: string\n /**\n * Locale prefix strategy:\n * - `'always'`: all locales get a URL prefix\n * - `'as-needed'`: source locale has no prefix, others do\n * - `'never'`: no locale prefix in URLs; locale determined by detection chain\n *\n * Default: `'as-needed'`\n */\n localePrefix?: 'always' | 'as-needed' | 'never'\n /**\n * When true, bare paths are internally rewritten to include the locale prefix.\n * Required when using `app/[locale]/` directory structure.\n *\n * Default: `false`\n */\n rewriteDefaultLocale?: boolean\n /**\n * When true, detected locale is persisted in a cookie.\n * Disabled by default to keep responses CDN-cacheable.\n */\n setCookie?: boolean\n /** Fine-grained cookie configuration for multi-domain / secure deployments. */\n cookieOptions?: CookieOptions\n /**\n * Set to false to disable automatic locale detection.\n * Bare paths will always use `sourceLocale` instead of detecting from cookie / Accept-Language.\n *\n * Default: `true`\n */\n localeDetection?: boolean\n /**\n * Custom locale detection function. Called when no locale is present in the URL path.\n * Return a locale string to override the default chain, or `undefined` to fall through.\n */\n detectLocale?: (req: NextRequest) => string | undefined\n /**\n * Domain-based locale routing. Each domain maps to a default locale.\n * Domain matching is checked before cookie/Accept-Language detection.\n *\n * @example\n * ```ts\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * { domain: 'example.co.jp', defaultLocale: 'ja' },\n * ]\n * ```\n */\n domains?: DomainConfig[]\n /**\n * Map internal paths to localized paths per locale.\n * Supports dynamic segments: `[param]` and `[...slug]`.\n *\n * @example\n * ```ts\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * }\n * ```\n */\n pathnames?: Record<string, Record<string, string>>\n /**\n * When true, adds `Link` response headers with `rel=\"alternate\"` hreflang\n * and `rel=\"canonical\"` for SEO.\n *\n * Default: `false`\n */\n alternateLinks?: boolean\n /**\n * Custom function to build alternate link entries. Overrides default `alternateLinks` behavior.\n * Return an array of `{ href, hreflang }` entries.\n */\n getAlternateLinks?: (context: {\n pathname: string\n locale: string\n locales: string[]\n origin: string\n basePath: string\n }) => AlternateLinkEntry[]\n /**\n * Called before the middleware returns a response.\n * Modify headers, cookies, or return a replacement response.\n */\n beforeResponse?: (context: {\n response: NextResponseInstance\n request: NextRequest\n locale: string\n type: 'redirect' | 'rewrite' | 'next'\n }) => NextResponseInstance | void | undefined\n}\n\n/** Minimal request shape required by the middleware. Compatible with Next.js NextRequest. */\ntype NextRequest = {\n nextUrl: { pathname: string; search: string; basePath?: string }\n url: string\n cookies: { get(name: string): { value: string } | undefined }\n headers: Headers\n}\n\ntype NextResponseStatic<R extends NextResponseInstance = NextResponseInstance> = {\n redirect(url: URL): R\n rewrite(url: URL, init?: Record<string, unknown>): R\n next(init?: Record<string, unknown>): R\n}\n\ntype NextResponseInstance = {\n headers: { set(name: string, value: string): void }\n}\n\n/**\n * Create an i18n middleware function for Next.js.\n */\nexport function createI18nMiddleware<R extends NextResponseInstance = NextResponseInstance>(\n config: I18nMiddlewareConfig & { NextResponse: NextResponseStatic<R> },\n) {\n const { NextResponse } = config\n const resolvedLocales: string[] = config.locales ?? _configLocales\n const resolvedSourceLocale: string = config.sourceLocale ?? _configSourceLocale\n const cookieName = config.cookieName ?? _configCookieName\n const localePrefix = config.localePrefix ?? 'as-needed'\n const rewriteDefaultLocale = config.rewriteDefaultLocale ?? false\n const setCookieEnabled = config.setCookie ?? false\n const cookieOpts: CookieOptions = config.cookieOptions ?? {}\n const localeDetectionEnabled = config.localeDetection ?? true\n const alternateLinksEnabled = config.alternateLinks ?? false\n const pathnamesMap = config.pathnames\n const domainsConfig = config.domains\n const beforeResponse = config.beforeResponse\n\n function finalizeResponse(\n response: R,\n request: NextRequest,\n locale: string,\n type: 'redirect' | 'rewrite' | 'next',\n pathLocale: string | null,\n ): R {\n response.headers.set(LOCALE_HEADER, locale)\n if (setCookieEnabled) {\n maybeSetCookie(response, request, locale, cookieName, pathLocale, cookieOpts)\n }\n if (alternateLinksEnabled || config.getAlternateLinks) {\n const linkHeader = config.getAlternateLinks\n ? buildCustomAlternateLinks(config.getAlternateLinks, request, locale, resolvedLocales)\n : buildAlternateLinks(request, resolvedLocales, resolvedSourceLocale, localePrefix, request.nextUrl.basePath ?? '', pathnamesMap)\n response.headers.set('Link', linkHeader)\n }\n if (beforeResponse) {\n const replacement = beforeResponse({ response, request, locale, type })\n if (replacement) return replacement as R\n }\n return response\n }\n\n return function i18nMiddleware(request: NextRequest): R {\n const locales = resolvedLocales\n const sourceLocale = resolvedSourceLocale\n const { pathname, search } = request.nextUrl\n const basePath = request.nextUrl.basePath ?? ''\n\n // Extract locale from URL path ('never' mode skips this)\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n const pathLocale = localePrefix === 'never' ? null : findLocale(firstSegment, locales)\n\n // Determine the active locale\n let locale: string\n\n if (pathLocale) {\n locale = pathLocale\n } else if (!localeDetectionEnabled) {\n locale = sourceLocale\n } else {\n // Detection chain: custom → domains → cookie → Accept-Language → default\n const custom = config.detectLocale?.(request)\n if (custom !== undefined && findLocale(custom, locales) !== null) {\n locale = findLocale(custom, locales)!\n } else if (domainsConfig) {\n const domainLocale = detectFromDomain(request, domainsConfig, locales)\n locale = domainLocale ?? detectLocale(request, locales, sourceLocale, cookieName)\n } else {\n locale = detectLocale(request, locales, sourceLocale, cookieName)\n }\n }\n\n // Build request headers\n const requestHeaders = new Headers(request.headers)\n requestHeaders.set(LOCALE_HEADER, locale)\n\n // ── 'never' mode ──────────────────────────────────────────────────────\n if (localePrefix === 'never') {\n if (rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${locale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', null,\n )\n }\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', null,\n )\n }\n\n // ── Pathnames mapping ─────────────────────────────────────────────────\n if (pathnamesMap && pathLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n const internalPath = resolveInternalPath(pathWithoutLocale, locale, pathnamesMap)\n\n if (internalPath) {\n const rewriteUrl = new URL(`${basePath}/${locale}${internalPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n const localizedPath = resolveLocalizedPath(pathWithoutLocale, locale, pathnamesMap)\n if (localizedPath && localizedPath !== pathWithoutLocale) {\n const redirectUrl = new URL(`${basePath}/${locale}${localizedPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n }\n\n // ── Case 1: No locale in path → redirect ──────────────────────────────\n if (!pathLocale && (localePrefix === 'always' || locale !== sourceLocale)) {\n // If pathnames configured, redirect to localized path\n let targetPath = pathname\n if (pathnamesMap) {\n const localized = resolveLocalizedPath(pathname, locale, pathnamesMap)\n if (localized) targetPath = localized\n }\n const redirectUrl = new URL(`${basePath}/${locale}${targetPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n\n // ── Case 2: as-needed + source locale + rewriteDefaultLocale ──────────\n if (!pathLocale && locale === sourceLocale && rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${sourceLocale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 3: as-needed, source locale has explicit prefix → strip ──────\n if (localePrefix === 'as-needed' && pathLocale === sourceLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n if (rewriteDefaultLocale) {\n const redirectUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n const rewriteUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 4/5: pass through ────────────────────────────────────────────\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', pathLocale,\n )\n }\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction findLocale(candidate: string, locales: string[]): string | null {\n if (!candidate) return null\n const lower = candidate.toLowerCase()\n return locales.find(l => l.toLowerCase() === lower) ?? null\n}\n\nfunction normalizeSlashes(path: string): string {\n return path.replace(/\\/+/g, '/') || '/'\n}\n\nfunction maybeSetCookie(\n response: NextResponseInstance,\n request: NextRequest,\n locale: string,\n cookieName: string,\n pathLocale: string | null,\n opts: CookieOptions,\n): void {\n if (pathLocale) return\n if (request.cookies.get(cookieName)?.value === locale) return\n\n const parts = [`${cookieName}=${encodeURIComponent(locale)}`]\n parts.push(`path=${opts.path ?? '/'}`)\n parts.push(`max-age=${opts.maxAge ?? 31536000}`)\n parts.push(`samesite=${opts.sameSite ?? 'lax'}`)\n if (opts.domain) parts.push(`domain=${opts.domain}`)\n if (opts.secure ?? request.url.startsWith('https')) parts.push('secure')\n response.headers.set('set-cookie', parts.join(';'))\n}\n\nfunction detectFromDomain(\n request: NextRequest,\n domains: DomainConfig[],\n locales: string[],\n): string | null {\n const host = request.headers.get('host')?.split(':')[0] ?? ''\n for (const d of domains) {\n if (host === d.domain || host.endsWith('.' + d.domain)) {\n const found = findLocale(d.defaultLocale, d.locales ?? locales)\n if (found) return found\n }\n }\n return null\n}\n\nfunction detectLocale(\n request: NextRequest,\n locales: string[],\n defaultLocale: string,\n cookieName: string,\n): string {\n const cookieLocale = request.cookies.get(cookieName)?.value\n if (cookieLocale) {\n const found = findLocale(cookieLocale, locales)\n if (found) return found\n }\n\n const acceptLang = request.headers.get('accept-language')\n if (acceptLang) {\n for (const part of acceptLang.split(',')) {\n const lang = part.split(';')[0]!.trim()\n const exact = findLocale(lang, locales)\n if (exact) return exact\n const prefix = lang.split('-')[0]!.toLowerCase()\n const match = locales.find(l => {\n const ll = l.toLowerCase()\n return ll === prefix || ll.startsWith(prefix + '-')\n })\n if (match) return match\n }\n }\n\n return defaultLocale\n}\n\nfunction stripLocalePrefix(pathname: string, locales: string[]): string {\n const segments = pathname.split('/')\n const first = segments[1] ?? ''\n if (findLocale(first, locales)) {\n return normalizeSlashes('/' + segments.slice(2).join('/'))\n }\n return pathname\n}\n\n// Path resolution utilities imported from shared routing module\n// (bundled by tsup, safe for Edge Runtime)\nimport { resolveInternalPath, resolveLocalizedPath } from './routing'\n\nfunction buildAlternateLinks(\n request: NextRequest,\n locales: string[],\n sourceLocale: string,\n localePrefix: 'always' | 'as-needed' | 'never',\n basePath: string,\n pathnames?: Record<string, Record<string, string>>,\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n\n const links = locales.map(loc => {\n let localePath: string\n if (localePrefix === 'never') {\n localePath = cleanPath\n } else if (localePrefix === 'as-needed' && loc === sourceLocale) {\n localePath = cleanPath\n } else {\n localePath = `/${loc}${cleanPath}`\n }\n if (pathnames) {\n const mapped = resolveLocalizedPath(cleanPath, loc, pathnames)\n if (mapped) {\n localePath = localePrefix === 'never' || (localePrefix === 'as-needed' && loc === sourceLocale)\n ? mapped\n : `/${loc}${mapped}`\n }\n }\n return `<${origin}${basePath}${localePath}>; rel=\"alternate\"; hreflang=\"${loc}\"`\n })\n\n // x-default\n const defaultPath = localePrefix === 'always' ? `/${sourceLocale}${cleanPath}` : cleanPath\n links.push(`<${origin}${basePath}${defaultPath}>; rel=\"alternate\"; hreflang=\"x-default\"`)\n\n return links.join(', ')\n}\n\nfunction buildCustomAlternateLinks(\n getAlternateLinks: NonNullable<I18nMiddlewareConfig['getAlternateLinks']>,\n request: NextRequest,\n locale: string,\n locales: string[],\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n const basePath = request.nextUrl.basePath ?? ''\n const entries = getAlternateLinks({ pathname: cleanPath, locale, locales, origin, basePath })\n return entries.map(e => `<${e.href}>; rel=\"alternate\"; hreflang=\"${e.hreflang}\"`).join(', ')\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * @module @fluenti/next/middleware\n *\n * Built-in i18n middleware for Next.js App Router.\n *\n * Uses `x-fluenti-locale` request header to pass locale from middleware to\n * server components — avoids `Set-Cookie` on every request (CDN-friendly).\n *\n * @example Minimal\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({ NextResponse })\n * ```\n *\n * @example With pathnames + alternateLinks + domains\n * ```ts\n * import { NextResponse } from 'next/server'\n * import { createI18nMiddleware } from '@fluenti/next/middleware'\n *\n * export default createI18nMiddleware({\n * NextResponse,\n * rewriteDefaultLocale: true,\n * alternateLinks: true,\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * },\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * ],\n * })\n * ```\n */\n\nimport {\n locales as _configLocales,\n sourceLocale as _configSourceLocale,\n cookieName as _configCookieName,\n} from '@fluenti/next/i18n-config'\n\n/** Header name used to pass resolved locale from middleware to RSC */\nexport const LOCALE_HEADER = 'x-fluenti-locale'\n\nexport interface CookieOptions {\n /** Cookie domain (e.g. '.example.com' for cross-subdomain) */\n domain?: string\n /** Secure flag (default: auto-detect from request URL) */\n secure?: boolean\n /** SameSite attribute (default: 'lax') */\n sameSite?: 'lax' | 'strict' | 'none'\n /** Max age in seconds (default: 31536000 = 1 year) */\n maxAge?: number\n /** Cookie path (default: '/') */\n path?: string\n}\n\nexport interface DomainConfig {\n /** Domain hostname (e.g. 'fr.example.com') */\n domain: string\n /** Default locale for this domain */\n defaultLocale: string\n /** Optional subset of locales available on this domain */\n locales?: string[]\n}\n\nexport interface AlternateLinkEntry {\n href: string\n hreflang: string\n}\n\nexport interface I18nMiddlewareConfig {\n /** Available locales. If omitted, reads from `fluenti.config.ts`. */\n locales?: string[]\n /** Source/default locale. If omitted, reads from `fluenti.config.ts`. */\n sourceLocale?: string\n /** Cookie name for reading user preference (default: 'locale') */\n cookieName?: string\n /**\n * Locale prefix strategy:\n * - `'always'`: all locales get a URL prefix\n * - `'as-needed'`: source locale has no prefix, others do\n * - `'never'`: no locale prefix in URLs; locale determined by detection chain\n *\n * Default: `'as-needed'`\n */\n localePrefix?: 'always' | 'as-needed' | 'never'\n /**\n * When true, bare paths are internally rewritten to include the locale prefix.\n * Required when using `app/[locale]/` directory structure.\n *\n * Default: `false`\n */\n rewriteDefaultLocale?: boolean\n /**\n * When true, detected locale is persisted in a cookie.\n * Disabled by default to keep responses CDN-cacheable.\n */\n setCookie?: boolean\n /** Fine-grained cookie configuration for multi-domain / secure deployments. */\n cookieOptions?: CookieOptions\n /**\n * Set to false to disable automatic locale detection.\n * Bare paths will always use `sourceLocale` instead of detecting from cookie / Accept-Language.\n *\n * Default: `true`\n */\n localeDetection?: boolean\n /**\n * Custom locale detection function. Called when no locale is present in the URL path.\n * Return a locale string to override the default chain, or `undefined` to fall through.\n */\n detectLocale?: (req: NextRequest) => string | undefined\n /**\n * Domain-based locale routing. Each domain maps to a default locale.\n * Domain matching is checked before cookie/Accept-Language detection.\n *\n * @example\n * ```ts\n * domains: [\n * { domain: 'fr.example.com', defaultLocale: 'fr' },\n * { domain: 'example.co.jp', defaultLocale: 'ja' },\n * ]\n * ```\n */\n domains?: DomainConfig[]\n /**\n * Map internal paths to localized paths per locale.\n * Supports dynamic segments: `[param]` and `[...slug]`.\n *\n * @example\n * ```ts\n * pathnames: {\n * '/about': { fr: '/a-propos' },\n * '/blog/[slug]': { fr: '/articles/[slug]' },\n * }\n * ```\n */\n pathnames?: Record<string, Record<string, string>>\n /**\n * When true, adds `Link` response headers with `rel=\"alternate\"` hreflang\n * and `rel=\"canonical\"` for SEO.\n *\n * Default: `false`\n */\n alternateLinks?: boolean\n /**\n * Custom function to build alternate link entries. Overrides default `alternateLinks` behavior.\n * Return an array of `{ href, hreflang }` entries.\n */\n getAlternateLinks?: (context: {\n pathname: string\n locale: string\n locales: string[]\n origin: string\n basePath: string\n }) => AlternateLinkEntry[]\n /**\n * Called before the middleware returns a response.\n * Modify headers, cookies, or return a replacement response.\n */\n beforeResponse?: (context: {\n response: NextResponseInstance\n request: NextRequest\n locale: string\n type: 'redirect' | 'rewrite' | 'next'\n }) => NextResponseInstance | void | undefined\n}\n\n/** Minimal request shape required by the middleware. Compatible with Next.js NextRequest. */\ntype NextRequest = {\n nextUrl: { pathname: string; search: string; basePath?: string }\n url: string\n cookies: { get(name: string): { value: string } | undefined }\n headers: Headers\n}\n\ntype NextResponseStatic<R extends NextResponseInstance = NextResponseInstance> = {\n redirect(url: URL): R\n rewrite(url: URL, init?: Record<string, unknown>): R\n next(init?: Record<string, unknown>): R\n}\n\ntype NextResponseInstance = {\n headers: { set(name: string, value: string): void }\n}\n\n/**\n * Create an i18n middleware function for Next.js.\n */\nexport function createI18nMiddleware<R extends NextResponseInstance = NextResponseInstance>(\n config: I18nMiddlewareConfig & { NextResponse: NextResponseStatic<R> },\n) {\n const { NextResponse } = config\n const resolvedLocales: string[] = config.locales ?? _configLocales\n const resolvedSourceLocale: string = config.sourceLocale ?? _configSourceLocale\n const cookieName = config.cookieName ?? _configCookieName\n const localePrefix = config.localePrefix ?? 'as-needed'\n const rewriteDefaultLocale = config.rewriteDefaultLocale ?? false\n const setCookieEnabled = config.setCookie ?? false\n const cookieOpts: CookieOptions = config.cookieOptions ?? {}\n const localeDetectionEnabled = config.localeDetection ?? true\n const alternateLinksEnabled = config.alternateLinks ?? false\n const pathnamesMap = config.pathnames\n const domainsConfig = config.domains\n const beforeResponse = config.beforeResponse\n\n function finalizeResponse(\n response: R,\n request: NextRequest,\n locale: string,\n type: 'redirect' | 'rewrite' | 'next',\n pathLocale: string | null,\n ): R {\n response.headers.set(LOCALE_HEADER, locale)\n if (setCookieEnabled) {\n maybeSetCookie(response, request, locale, cookieName, pathLocale, cookieOpts)\n }\n if (alternateLinksEnabled || config.getAlternateLinks) {\n const linkHeader = config.getAlternateLinks\n ? buildCustomAlternateLinks(config.getAlternateLinks, request, locale, resolvedLocales)\n : buildAlternateLinks(request, resolvedLocales, resolvedSourceLocale, localePrefix, request.nextUrl.basePath ?? '', pathnamesMap)\n response.headers.set('Link', linkHeader)\n }\n if (beforeResponse) {\n const replacement = beforeResponse({ response, request, locale, type })\n if (replacement) return replacement as R\n }\n return response\n }\n\n return function i18nMiddleware(request: NextRequest): R {\n const locales = resolvedLocales\n const sourceLocale = resolvedSourceLocale\n const { pathname, search } = request.nextUrl\n const basePath = request.nextUrl.basePath ?? ''\n\n // Extract locale from URL path ('never' mode skips this)\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n const pathLocale = localePrefix === 'never' ? null : findLocale(firstSegment, locales)\n\n // Determine the active locale\n let locale: string\n\n if (pathLocale) {\n locale = pathLocale\n } else if (!localeDetectionEnabled) {\n locale = sourceLocale\n } else {\n // Detection chain: custom → domains → cookie → Accept-Language → default\n const custom = config.detectLocale?.(request)\n if (custom !== undefined && findLocale(custom, locales) !== null) {\n locale = findLocale(custom, locales)!\n } else if (domainsConfig) {\n const domainLocale = detectFromDomain(request, domainsConfig, locales)\n locale = domainLocale ?? detectLocale(request, locales, sourceLocale, cookieName)\n } else {\n locale = detectLocale(request, locales, sourceLocale, cookieName)\n }\n }\n\n // Build request headers\n const requestHeaders = new Headers(request.headers)\n requestHeaders.set(LOCALE_HEADER, locale)\n\n // ── 'never' mode ──────────────────────────────────────────────────────\n if (localePrefix === 'never') {\n if (rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${locale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', null,\n )\n }\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', null,\n )\n }\n\n // ── Pathnames mapping ─────────────────────────────────────────────────\n if (pathnamesMap && pathLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n const internalPath = resolveInternalPath(pathWithoutLocale, locale, pathnamesMap)\n\n if (internalPath) {\n const rewriteUrl = new URL(`${basePath}/${locale}${internalPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n const localizedPath = resolveLocalizedPath(pathWithoutLocale, locale, pathnamesMap)\n if (localizedPath && localizedPath !== pathWithoutLocale) {\n const redirectUrl = new URL(`${basePath}/${locale}${localizedPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n }\n\n // ── Case 1: No locale in path → redirect ──────────────────────────────\n if (!pathLocale && (localePrefix === 'always' || locale !== sourceLocale)) {\n // If pathnames configured, redirect to localized path\n let targetPath = pathname\n if (pathnamesMap) {\n const localized = resolveLocalizedPath(pathname, locale, pathnamesMap)\n if (localized) targetPath = localized\n }\n const redirectUrl = new URL(`${basePath}/${locale}${targetPath}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n\n // ── Case 2: as-needed + source locale + rewriteDefaultLocale ──────────\n if (!pathLocale && locale === sourceLocale && rewriteDefaultLocale) {\n const rewriteUrl = new URL(`${basePath}/${sourceLocale}${pathname}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 3: as-needed, source locale has explicit prefix → strip ──────\n if (localePrefix === 'as-needed' && pathLocale === sourceLocale) {\n const pathWithoutLocale = normalizeSlashes('/' + segments.slice(2).join('/'))\n if (rewriteDefaultLocale) {\n const redirectUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.redirect(redirectUrl),\n request, locale, 'redirect', pathLocale,\n )\n }\n const rewriteUrl = new URL(`${basePath}${pathWithoutLocale}${search}`, request.url)\n return finalizeResponse(\n NextResponse.rewrite(rewriteUrl, { request: { headers: requestHeaders } }),\n request, locale, 'rewrite', pathLocale,\n )\n }\n\n // ── Case 4/5: pass through ────────────────────────────────────────────\n return finalizeResponse(\n NextResponse.next({ request: { headers: requestHeaders } }),\n request, locale, 'next', pathLocale,\n )\n }\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction findLocale(candidate: string, locales: string[]): string | null {\n if (!candidate) return null\n const lower = candidate.toLowerCase()\n return locales.find(l => l.toLowerCase() === lower) ?? null\n}\n\nfunction normalizeSlashes(path: string): string {\n return path.replace(/\\/+/g, '/') || '/'\n}\n\nfunction maybeSetCookie(\n response: NextResponseInstance,\n request: NextRequest,\n locale: string,\n cookieName: string,\n pathLocale: string | null,\n opts: CookieOptions,\n): void {\n if (pathLocale) return\n if (request.cookies.get(cookieName)?.value === locale) return\n\n const parts = [`${cookieName}=${encodeURIComponent(locale)}`]\n parts.push(`path=${opts.path ?? '/'}`)\n parts.push(`max-age=${opts.maxAge ?? 31536000}`)\n parts.push(`samesite=${opts.sameSite ?? 'lax'}`)\n if (opts.domain) parts.push(`domain=${opts.domain}`)\n if (opts.secure ?? request.url.startsWith('https')) parts.push('secure')\n response.headers.set('set-cookie', parts.join(';'))\n}\n\nfunction detectFromDomain(\n request: NextRequest,\n domains: DomainConfig[],\n locales: string[],\n): string | null {\n const host = request.headers.get('host')?.split(':')[0] ?? ''\n for (const d of domains) {\n if (host === d.domain || host.endsWith('.' + d.domain)) {\n const found = findLocale(d.defaultLocale, d.locales ?? locales)\n if (found) return found\n }\n }\n return null\n}\n\nfunction detectLocale(\n request: NextRequest,\n locales: string[],\n defaultLocale: string,\n cookieName: string,\n): string {\n const cookieLocale = request.cookies.get(cookieName)?.value\n if (cookieLocale) {\n const found = findLocale(cookieLocale, locales)\n if (found) return found\n }\n\n const acceptLang = request.headers.get('accept-language')\n if (acceptLang) {\n for (const part of acceptLang.split(',')) {\n const lang = part.split(';')[0]!.trim()\n const exact = findLocale(lang, locales)\n if (exact) return exact\n const prefix = lang.split('-')[0]!.toLowerCase()\n const match = locales.find(l => {\n const ll = l.toLowerCase()\n return ll === prefix || ll.startsWith(prefix + '-')\n })\n if (match) return match\n }\n }\n\n return defaultLocale\n}\n\nfunction stripLocalePrefix(pathname: string, locales: string[]): string {\n const segments = pathname.split('/')\n const first = segments[1] ?? ''\n if (findLocale(first, locales)) {\n return normalizeSlashes('/' + segments.slice(2).join('/'))\n }\n return pathname\n}\n\n// Path resolution utilities imported from shared routing module\n// (bundled by tsup, safe for Edge Runtime)\nimport { resolveInternalPath, resolveLocalizedPath } from './routing'\n\nfunction buildAlternateLinks(\n request: NextRequest,\n locales: string[],\n sourceLocale: string,\n localePrefix: 'always' | 'as-needed' | 'never',\n basePath: string,\n pathnames?: Record<string, Record<string, string>>,\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n const firstSegment = request.nextUrl.pathname.split('/')[1] ?? ''\n const requestLocale = localePrefix === 'never'\n ? sourceLocale\n : findLocale(firstSegment, locales) ?? sourceLocale\n const internalPath = pathnames\n ? resolveInternalPath(cleanPath, requestLocale, pathnames) ?? cleanPath\n : cleanPath\n\n const links = locales.map(loc => {\n const outwardPath = pathnames\n ? resolveLocalizedPath(internalPath, loc, pathnames) ?? internalPath\n : internalPath\n let localePath: string\n if (localePrefix === 'never') {\n localePath = outwardPath\n } else if (localePrefix === 'as-needed' && loc === sourceLocale) {\n localePath = outwardPath\n } else {\n localePath = `/${loc}${outwardPath}`\n }\n return `<${origin}${basePath}${localePath}>; rel=\"alternate\"; hreflang=\"${loc}\"`\n })\n\n // x-default\n const sourceOutwardPath = pathnames\n ? resolveLocalizedPath(internalPath, sourceLocale, pathnames) ?? internalPath\n : internalPath\n const defaultPath = localePrefix === 'always' ? `/${sourceLocale}${sourceOutwardPath}` : sourceOutwardPath\n links.push(`<${origin}${basePath}${defaultPath}>; rel=\"alternate\"; hreflang=\"x-default\"`)\n\n return links.join(', ')\n}\n\nfunction buildCustomAlternateLinks(\n getAlternateLinks: NonNullable<I18nMiddlewareConfig['getAlternateLinks']>,\n request: NextRequest,\n locale: string,\n locales: string[],\n): string {\n const origin = new URL(request.url).origin\n const cleanPath = stripLocalePrefix(request.nextUrl.pathname, locales)\n const basePath = request.nextUrl.basePath ?? ''\n const entries = getAlternateLinks({ pathname: cleanPath, locale, locales, origin, basePath })\n return entries.map(e => `<${e.href}>; rel=\"alternate\"; hreflang=\"${e.hreflang}\"`).join(', ')\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./routing-Dy57-Uao.cjs`);let t=require(`@fluenti/react`),n=require(`react`),r=require(`@fluenti/next/i18n-config`),i=require(`next/navigation`);e.n();function a(t){let{locales:r,sourceLocale:i,localePrefix:a=`as-needed`,pathnames:o}=t;function s(t,n){let r=t;if(o){let i=e.i(t,n,o);i&&(r=i)}return a===`never`||a===`as-needed`&&n===i?r:`/${n}${r}`}function c(e){if(a===`never`)return e;let t=e.split(`/`),n=(t[1]??``).toLowerCase();return r.some(e=>e.toLowerCase()===n)?`/`+t.slice(2).join(`/`)||`/`:e}let l=(0,n.forwardRef)(function({href:e,locale:t,...r},a){let o=require(`next/link`).default,c=i;try{let{useI18n:e}=require(`@fluenti/react`);c=e().locale}catch{}return(0,n.createElement)(o,{ref:a,href:s(String(e),String(t??c)),...r})});l.displayName=`I18nLink`;function u(){let{useRouter:e}=require(`next/navigation`),{useI18n:t}=require(`@fluenti/react`),n=e(),{locale:r}=t();return{push(e,t){n.push(s(String(e),String(t?.locale??r)))},replace(e,t){n.replace(s(String(e),String(t?.locale??r)))},back:()=>n.back(),forward:()=>n.forward(),refresh:()=>n.refresh(),prefetch:e=>n.prefetch(e)}}function d(e,t){let{redirect:n}=require(`next/navigation`);return n(s(String(e),String(t??i)))}function f(){let{usePathname:e}=require(`next/navigation`);return c(e())}function p(e,t){return s(String(e),String(t))}return{Link:l,useRouter:u,redirect:d,usePathname:f,getPathname:p}}e.n();function o(e,t,n){let r=n?.sourceLocale??`en`,i=n?.localePrefix??`as-needed`,a=e.split(`/`),o=a[1]??``,s=(n?.locales?n.locales.includes(o):/^[a-z]{2}(-[A-Za-z]{2,})?$/.test(o))?`/`+a.slice(2).join(`/`):e;return i===`never`||i!==`always`&&t===r?s||`/`:`/${t}${s}`}function s(e){let n=(0,i.useRouter)(),a=(0,i.usePathname)(),{locale:s,setLocale:c,getLocales:l}=(0,t.useI18n)(),u=l(),d=e?.sourceLocale??u[0]??`en`,f=e?.cookieName??r.cookieName,p=e?.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=o(a,e,{sourceLocale:d,locales:u,localePrefix:p});n.push(t),n.refresh()},currentLocale:s,locales:u,sourceLocale:d}}function c(t){let{routing:n,baseUrl:r=``}=t,a=(0,i.usePathname)(),o=a.split(`/`),s=o[1]??``,c=n.locales.some(e=>e.toLowerCase()===s.toLowerCase())?`/`+o.slice(2).join(`/`)||`/`:a,l=n.locales.map(t=>{let i=c;if(n.pathnames){let{resolveLocalizedPath:r}=(e.n(),e.o(e.a)),a=r(c,t,n.pathnames);a&&(i=a)}let a;return a=n.localePrefix===`never`||n.localePrefix!==`always`&&t===n.sourceLocale?`${r}${i}`:`${r}/${t}${i}`,{hreflang:t,href:a}}),u=n.localePrefix===`always`?`${r}/${n.sourceLocale}${c}`:`${r}${c}`;return l.push({hreflang:`x-default`,href:u}),l}exports.createNavigation=a,exports.defineRouting=e.t,exports.getLocalePath=o,exports.resolveLocalizedPath=e.i,exports.useAlternateLinks=c,exports.useLocaleSwitcher=s;
2
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./routing-D4p8HvMH.cjs`);let t=require(`@fluenti/react`),n=require(`react`),r=require(`@fluenti/next/i18n-config`),i=require(`next/navigation`);function a(t){let{locales:r,sourceLocale:i,localePrefix:a=`as-needed`,pathnames:o}=t;function s(t,n){let r=t;if(o){let i=e.r(t,n,o);i&&(r=i)}return a===`never`||a===`as-needed`&&n===i?r:`/${n}${r}`}function c(e){if(a===`never`)return e;let t=e.split(`/`),n=(t[1]??``).toLowerCase();return r.some(e=>e.toLowerCase()===n)?`/`+t.slice(2).join(`/`)||`/`:e}let l=(0,n.forwardRef)(function({href:e,locale:t,...r},a){let o=require(`next/link`).default,c=i;try{let{useI18n:e}=require(`@fluenti/react`);c=e().locale}catch{}return(0,n.createElement)(o,{ref:a,href:s(String(e),String(t??c)),...r})});l.displayName=`I18nLink`;function u(){let{useRouter:e}=require(`next/navigation`),{useI18n:t}=require(`@fluenti/react`),n=e(),{locale:r}=t();return{push(e,t){n.push(s(String(e),String(t?.locale??r)))},replace(e,t){n.replace(s(String(e),String(t?.locale??r)))},back:()=>n.back(),forward:()=>n.forward(),refresh:()=>n.refresh(),prefetch:e=>n.prefetch(e)}}function d(e,t){let{redirect:n}=require(`next/navigation`);return n(s(String(e),String(t??i)))}function f(){let{usePathname:e}=require(`next/navigation`);return c(e())}function p(e,t){return s(String(e),String(t))}return{Link:l,useRouter:u,redirect:d,usePathname:f,getPathname:p}}function o(e,t,n){let r=n?.sourceLocale??`en`,i=n?.localePrefix??`as-needed`,a=e.split(`/`),o=a[1]??``,s=n?.locales&&n.locales.some(e=>e.toLowerCase()===o.toLowerCase())?`/`+a.slice(2).join(`/`):e;return i===`never`||i!==`always`&&t===r?s||`/`:`/${t}${s}`}function s(e){let n=(0,i.useRouter)(),a=(0,i.usePathname)(),{locale:s,setLocale:c,getLocales:l}=(0,t.useI18n)(),u=l(),d=e?.sourceLocale??u[0]??`en`,f=e?.cookieName??r.cookieName,p=e?.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=o(a,e,{sourceLocale:d,locales:u,localePrefix:p});n.push(t),n.refresh()},currentLocale:s,locales:u,sourceLocale:d}}function c(n){let{routing:r,baseUrl:a=``}=n,o=(0,i.usePathname)(),s=r.sourceLocale;try{s=(0,t.useI18n)().locale}catch{}let c=o.split(`/`),l=c[1]??``,u=r.localePrefix===`never`?null:r.locales.find(e=>e.toLowerCase()===l.toLowerCase())??null,d=u?`/`+c.slice(2).join(`/`)||`/`:o,f=u??s??r.sourceLocale,p=r.pathnames?e.n(d,f,r.pathnames)??d:d,m=r.locales.map(t=>{let n=r.pathnames?e.r(p,t,r.pathnames)??p:p,i;return i=r.localePrefix===`never`||r.localePrefix!==`always`&&t===r.sourceLocale?`${a}${n}`:`${a}/${t}${n}`,{hreflang:t,href:i}}),h=r.pathnames?e.r(p,r.sourceLocale,r.pathnames)??p:p,g=r.localePrefix===`always`?`${a}/${r.sourceLocale}${h}`:`${a}${h}`;return m.push({hreflang:`x-default`,href:g}),m}exports.createNavigation=a,exports.defineRouting=e.t,exports.getLocalePath=o,exports.resolveLocalizedPath=e.r,exports.useAlternateLinks=c,exports.useLocaleSwitcher=s;
3
3
  //# sourceMappingURL=navigation.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"navigation.cjs","names":[],"sources":["../src/create-navigation.tsx","../src/navigation.ts"],"sourcesContent":["/**\n * @module @fluenti/next/navigation\n *\n * Type-safe navigation factory for Next.js i18n routing.\n *\n * @example\n * ```tsx\n * // src/lib/navigation.ts\n * import { createNavigation } from '@fluenti/next/navigation'\n *\n * export const { Link, useRouter, redirect, usePathname, getPathname } = createNavigation({\n * locales: ['en', 'fr', 'ja'] as const,\n * sourceLocale: 'en',\n * localePrefix: 'as-needed',\n * pathnames: {\n * '/': { fr: '/', ja: '/' },\n * '/about': { fr: '/a-propos', ja: '/about' },\n * '/blog/[slug]': { fr: '/articles/[slug]', ja: '/blog/[slug]' },\n * },\n * })\n * ```\n */\n'use client'\n\nimport { createElement, forwardRef } from 'react'\nimport type { ReactNode } from 'react'\nimport { resolveLocalizedPath } from './routing'\nimport type { RoutingConfig } from './routing'\n\nexport type { RoutingConfig }\n\ninterface NavigationLinkProps<P extends string = string, L extends string = string> {\n href: P | (string & Record<never, never>)\n locale?: L\n children?: ReactNode\n prefetch?: boolean | null\n replace?: boolean\n scroll?: boolean\n target?: string\n className?: string\n [key: string]: unknown\n}\n\ninterface TypedRouter<P extends string = string, L extends string = string> {\n push(href: P | (string & Record<never, never>), options?: { locale?: L }): void\n replace(href: P | (string & Record<never, never>), options?: { locale?: L }): void\n back(): void\n forward(): void\n refresh(): void\n prefetch(href: string): void\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────\n\nexport function createNavigation<\n const L extends string,\n const P extends string,\n>(routing: RoutingConfig<L, P>) {\n const { locales, sourceLocale, localePrefix = 'as-needed', pathnames } = routing\n\n function resolvePath(href: string, locale: string): string {\n // Apply pathnames mapping if configured (supports [param] and [...slug])\n let resolved = href\n if (pathnames) {\n const mapped = resolveLocalizedPath(href, locale, pathnames as Record<string, Record<string, string>>)\n if (mapped) resolved = mapped\n }\n\n // Apply locale prefix\n if (localePrefix === 'never') return resolved\n if (localePrefix === 'as-needed' && locale === sourceLocale) return resolved\n return `/${locale}${resolved}`\n }\n\n function stripPrefix(pathname: string): string {\n if (localePrefix === 'never') return pathname\n const segments = pathname.split('/')\n const first = segments[1] ?? ''\n const lower = first.toLowerCase()\n const isLocale = locales.some(l => l.toLowerCase() === lower)\n if (isLocale) return '/' + segments.slice(2).join('/') || '/'\n return pathname\n }\n\n // ── Link component ────────────────────────────────────────────────────\n\n const Link = forwardRef<HTMLAnchorElement, NavigationLinkProps<P, L>>(\n function I18nLink({ href, locale: localeProp, ...rest }, ref) {\n // Dynamic imports to avoid SSR issues — these are resolved at runtime\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const NextLink = require('next/link').default\n let currentLocale = sourceLocale\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useI18n } = require('@fluenti/react')\n const i18n = useI18n()\n currentLocale = i18n.locale\n } catch {\n // Outside I18nProvider, use sourceLocale\n }\n const locale = localeProp ?? currentLocale\n const resolvedHref = resolvePath(String(href), String(locale))\n return createElement(NextLink, { ref, href: resolvedHref, ...rest })\n },\n )\n Link.displayName = 'I18nLink'\n\n // ── useRouter hook ────────────────────────────────────────────────────\n\n function useRouter(): TypedRouter<P, L> {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useRouter: useNextRouter } = require('next/navigation')\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useI18n } = require('@fluenti/react')\n\n const nextRouter = useNextRouter()\n const { locale } = useI18n()\n\n return {\n push(href: P | string, options?: { locale?: L }) {\n nextRouter.push(resolvePath(String(href), String(options?.locale ?? locale)))\n },\n replace(href: P | string, options?: { locale?: L }) {\n nextRouter.replace(resolvePath(String(href), String(options?.locale ?? locale)))\n },\n back: () => nextRouter.back(),\n forward: () => nextRouter.forward(),\n refresh: () => nextRouter.refresh(),\n prefetch: (href: string) => nextRouter.prefetch(href),\n }\n }\n\n // ── redirect function (server-side) ───────────────────────────────────\n\n function redirect(href: P | (string & Record<never, never>), locale?: L): never {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { redirect: nextRedirect } = require('next/navigation') as { redirect: (url: string) => never }\n const resolvedLocale = locale ?? sourceLocale\n return nextRedirect(resolvePath(String(href), String(resolvedLocale)))\n }\n\n // ── usePathname hook ──────────────────────────────────────────────────\n\n function usePathname(): string {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { usePathname: useNextPathname } = require('next/navigation')\n const pathname = useNextPathname()\n return stripPrefix(pathname)\n }\n\n // ── getPathname utility ───────────────────────────────────────────────\n\n function getPathname(href: P | (string & Record<never, never>), locale: L): string {\n return resolvePath(String(href), String(locale))\n }\n\n return { Link, useRouter, redirect, usePathname, getPathname }\n}\n","'use client'\n\n/**\n * @module @fluenti/next/navigation\n *\n * Navigation utilities for locale-aware routing in Next.js App Router.\n *\n * @example\n * ```tsx\n * import { useLocaleSwitcher } from '@fluenti/next/navigation'\n *\n * function LanguagePicker() {\n * const { switchLocale, currentLocale, locales } = useLocaleSwitcher()\n * return (\n * <select value={currentLocale} onChange={(e) => switchLocale(e.target.value)}>\n * {locales.map((l) => <option key={l} value={l}>{l}</option>)}\n * </select>\n * )\n * }\n * ```\n */\nimport { useRouter, usePathname } from 'next/navigation'\nimport { useI18n } from '@fluenti/react'\nimport { cookieName as _configCookieName } from '@fluenti/next/i18n-config'\n\nexport interface GetLocalePathOptions {\n /** Source/default locale (no prefix in as-needed mode) */\n sourceLocale?: string\n /**\n * Known locale codes (e.g. ['en', 'fr', 'ja']).\n * When provided, the existing prefix is only stripped when it's an actual locale —\n * preventing false matches on generic 2-letter path segments like /my or /us.\n */\n locales?: string[]\n /**\n * Locale prefix strategy. Matches the middleware `localePrefix` setting.\n * - `'as-needed'` (default): source locale has no prefix (`/about` for en, `/fr/about` for fr)\n * - `'always'`: all locales get a prefix (`/en/about`, `/fr/about`)\n * - `'never'`: no locale prefix in URLs\n */\n localePrefix?: 'always' | 'as-needed' | 'never'\n}\n\n/**\n * Get the locale-prefixed path for a given pathname and locale.\n *\n * Pure function — works on both server and client.\n *\n * @example\n * ```ts\n * getLocalePath('/about', 'fr') // → '/fr/about'\n * getLocalePath('/about', 'en') // → '/about' (source locale, no prefix)\n * getLocalePath('/fr/about', 'en') // → '/about'\n * getLocalePath('/fr/about', 'ja') // → '/ja/about'\n * getLocalePath('/about', 'en', { localePrefix: 'always' }) // → '/en/about'\n * ```\n */\nexport function getLocalePath(\n pathname: string,\n locale: string,\n options?: GetLocalePathOptions,\n): string {\n const sourceLocale = options?.sourceLocale ?? 'en'\n const localePrefix = options?.localePrefix ?? 'as-needed'\n\n // Strip existing locale prefix if present\n const segments = pathname.split('/')\n const firstSegment = segments[1] ?? ''\n\n // Check if the first segment is a locale prefix.\n // If a locales list is provided, do an exact membership check to avoid false positives\n // on generic 2-letter path segments (e.g. /my/page or /us/pricing).\n // Otherwise fall back to the heuristic regex.\n const hasLocalePrefix = options?.locales\n ? options.locales.includes(firstSegment)\n : /^[a-z]{2}(-[A-Za-z]{2,})?$/.test(firstSegment)\n const pathWithoutLocale = hasLocalePrefix\n ? '/' + segments.slice(2).join('/')\n : pathname\n\n // 'never' mode: no prefix for any locale\n if (localePrefix === 'never') {\n return pathWithoutLocale || '/'\n }\n\n // In 'as-needed' mode, source locale gets no prefix\n if (localePrefix !== 'always' && locale === sourceLocale) {\n return pathWithoutLocale || '/'\n }\n\n return `/${locale}${pathWithoutLocale}`\n}\n\n/**\n * Hook for switching locales in Next.js App Router.\n *\n * Sets a cookie to remember user preference, navigates to the new locale path,\n * and triggers a server component refresh.\n */\nexport function useLocaleSwitcher(options?: {\n /** Override the source/default locale instead of inferring from locales[0]. */\n sourceLocale?: string\n /**\n * Cookie name used by the middleware for locale preference.\n * Defaults to the value from `fluenti.config.ts` (auto-read at build time).\n * Must match the middleware `cookieName` option.\n */\n cookieName?: string\n /** Locale prefix strategy — must match the middleware `localePrefix` option. */\n localePrefix?: 'always' | 'as-needed' | 'never'\n}) {\n const router = useRouter()\n const pathname = usePathname()\n const { locale, setLocale, getLocales } = useI18n()\n\n // Read locales from I18nProvider context (works on client without fs)\n const locales = getLocales()\n const sourceLocale = options?.sourceLocale ?? locales[0] ?? 'en'\n const cookieName = options?.cookieName ?? _configCookieName\n const localePrefix = options?.localePrefix ?? 'as-needed'\n\n const switchLocale = (newLocale: string) => {\n // Validate locale against known locales to prevent cookie injection\n if (!locales.includes(newLocale)) {\n if (typeof process !== 'undefined' && process.env?.['NODE_ENV'] !== 'production') {\n console.warn(`[fluenti] switchLocale: invalid locale \"${newLocale}\"`)\n }\n return\n }\n // 1. Set cookie to remember preference (uses configured cookie name)\n document.cookie = `${cookieName}=${encodeURIComponent(newLocale)};path=/;max-age=31536000;samesite=lax`\n // 2. Update React context\n setLocale(newLocale)\n // 3. Navigate to new locale path\n const newPath = getLocalePath(pathname, newLocale, { sourceLocale, locales, localePrefix })\n router.push(newPath)\n // 4. Refresh server components\n router.refresh()\n }\n\n return {\n switchLocale,\n currentLocale: locale,\n locales,\n sourceLocale,\n }\n}\n\n// Re-export createNavigation and routing utilities\nexport { createNavigation } from './create-navigation'\nexport { defineRouting, resolveLocalizedPath } from './routing'\nexport type { RoutingConfig } from './routing'\n\n// ── useAlternateLinks ─────────────────────────────────────────────────────\n\nexport interface AlternateLink {\n hreflang: string\n href: string\n}\n\n/**\n * Generate alternate link entries for SEO `<head>` tags.\n *\n * Returns an array of `{ hreflang, href }` entries for all configured locales\n * plus `x-default`. Use in `<head>` for hreflang tags.\n *\n * @example\n * ```tsx\n * import { useAlternateLinks } from '@fluenti/next/navigation'\n * import { routing } from '@/i18n/routing'\n *\n * export function Head() {\n * const links = useAlternateLinks({ routing, baseUrl: 'https://example.com' })\n * return (\n * <head>\n * {links.map(l => (\n * <link key={l.hreflang} rel=\"alternate\" hreflang={l.hreflang} href={l.href} />\n * ))}\n * </head>\n * )\n * }\n * ```\n */\nexport function useAlternateLinks(options: {\n routing: { locales: readonly string[]; sourceLocale: string; localePrefix?: 'always' | 'as-needed' | 'never'; pathnames?: Record<string, Record<string, string>> }\n baseUrl?: string\n}): AlternateLink[] {\n const { routing: r, baseUrl = '' } = options\n const pathname = usePathname()\n // Strip locale prefix from current path\n const segments = pathname.split('/')\n const firstSeg = segments[1] ?? ''\n const isLocalePrefix = (r.locales as string[]).some(l => l.toLowerCase() === firstSeg.toLowerCase())\n const cleanPath = isLocalePrefix ? '/' + segments.slice(2).join('/') || '/' : pathname\n\n const links: AlternateLink[] = (r.locales as string[]).map(loc => {\n let localePath = cleanPath\n if (r.pathnames) {\n const { resolveLocalizedPath: resolve } = require('./routing') as typeof import('./routing')\n const mapped = resolve(cleanPath, loc, r.pathnames as Record<string, Record<string, string>>)\n if (mapped) localePath = mapped\n }\n\n let href: string\n if (r.localePrefix === 'never') {\n href = `${baseUrl}${localePath}`\n } else if (r.localePrefix !== 'always' && loc === r.sourceLocale) {\n href = `${baseUrl}${localePath}`\n } else {\n href = `${baseUrl}/${loc}${localePath}`\n }\n\n return { hreflang: loc, href }\n })\n\n // x-default\n const defaultPath = r.localePrefix === 'always' ? `${baseUrl}/${r.sourceLocale}${cleanPath}` : `${baseUrl}${cleanPath}`\n links.push({ hreflang: 'x-default', href: defaultPath })\n\n return links\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"navigation.cjs","names":[],"sources":["../src/create-navigation.tsx","../src/navigation.ts"],"sourcesContent":["/**\n * @module @fluenti/next/navigation\n *\n * Type-safe navigation factory for Next.js i18n routing.\n *\n * @example\n * ```tsx\n * // src/lib/navigation.ts\n * import { createNavigation } from '@fluenti/next/navigation'\n *\n * export const { Link, useRouter, redirect, usePathname, getPathname } = createNavigation({\n * locales: ['en', 'fr', 'ja'] as const,\n * sourceLocale: 'en',\n * localePrefix: 'as-needed',\n * pathnames: {\n * '/': { fr: '/', ja: '/' },\n * '/about': { fr: '/a-propos', ja: '/about' },\n * '/blog/[slug]': { fr: '/articles/[slug]', ja: '/blog/[slug]' },\n * },\n * })\n * ```\n */\n'use client'\n\nimport { createElement, forwardRef } from 'react'\nimport type { ReactNode } from 'react'\nimport { resolveLocalizedPath } from './routing'\nimport type { RoutingConfig } from './routing'\n\nexport type { RoutingConfig }\n\ninterface NavigationLinkProps<P extends string = string, L extends string = string> {\n href: P | (string & Record<never, never>)\n locale?: L\n children?: ReactNode\n prefetch?: boolean | null\n replace?: boolean\n scroll?: boolean\n target?: string\n className?: string\n [key: string]: unknown\n}\n\ninterface TypedRouter<P extends string = string, L extends string = string> {\n push(href: P | (string & Record<never, never>), options?: { locale?: L }): void\n replace(href: P | (string & Record<never, never>), options?: { locale?: L }): void\n back(): void\n forward(): void\n refresh(): void\n prefetch(href: string): void\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────\n\nexport function createNavigation<\n const L extends string,\n const P extends string,\n>(routing: RoutingConfig<L, P>) {\n const { locales, sourceLocale, localePrefix = 'as-needed', pathnames } = routing\n\n function resolvePath(href: string, locale: string): string {\n // Apply pathnames mapping if configured (supports [param] and [...slug])\n let resolved = href\n if (pathnames) {\n const mapped = resolveLocalizedPath(href, locale, pathnames as Record<string, Record<string, string>>)\n if (mapped) resolved = mapped\n }\n\n // Apply locale prefix\n if (localePrefix === 'never') return resolved\n if (localePrefix === 'as-needed' && locale === sourceLocale) return resolved\n return `/${locale}${resolved}`\n }\n\n function stripPrefix(pathname: string): string {\n if (localePrefix === 'never') return pathname\n const segments = pathname.split('/')\n const first = segments[1] ?? ''\n const lower = first.toLowerCase()\n const isLocale = locales.some(l => l.toLowerCase() === lower)\n if (isLocale) return '/' + segments.slice(2).join('/') || '/'\n return pathname\n }\n\n // ── Link component ────────────────────────────────────────────────────\n\n const Link = forwardRef<HTMLAnchorElement, NavigationLinkProps<P, L>>(\n function I18nLink({ href, locale: localeProp, ...rest }, ref) {\n // Dynamic imports to avoid SSR issues — these are resolved at runtime\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const NextLink = require('next/link').default\n let currentLocale = sourceLocale\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useI18n } = require('@fluenti/react')\n const i18n = useI18n()\n currentLocale = i18n.locale\n } catch {\n // Outside I18nProvider, use sourceLocale\n }\n const locale = localeProp ?? currentLocale\n const resolvedHref = resolvePath(String(href), String(locale))\n return createElement(NextLink, { ref, href: resolvedHref, ...rest })\n },\n )\n Link.displayName = 'I18nLink'\n\n // ── useRouter hook ────────────────────────────────────────────────────\n\n function useRouter(): TypedRouter<P, L> {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useRouter: useNextRouter } = require('next/navigation')\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useI18n } = require('@fluenti/react')\n\n const nextRouter = useNextRouter()\n const { locale } = useI18n()\n\n return {\n push(href: P | string, options?: { locale?: L }) {\n nextRouter.push(resolvePath(String(href), String(options?.locale ?? locale)))\n },\n replace(href: P | string, options?: { locale?: L }) {\n nextRouter.replace(resolvePath(String(href), String(options?.locale ?? locale)))\n },\n back: () => nextRouter.back(),\n forward: () => nextRouter.forward(),\n refresh: () => nextRouter.refresh(),\n prefetch: (href: string) => nextRouter.prefetch(href),\n }\n }\n\n // ── redirect function (server-side) ───────────────────────────────────\n\n function redirect(href: P | (string & Record<never, never>), locale?: L): never {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { redirect: nextRedirect } = require('next/navigation') as { redirect: (url: string) => never }\n const resolvedLocale = locale ?? sourceLocale\n return nextRedirect(resolvePath(String(href), String(resolvedLocale)))\n }\n\n // ── usePathname hook ──────────────────────────────────────────────────\n\n function usePathname(): string {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { usePathname: useNextPathname } = require('next/navigation')\n const pathname = useNextPathname()\n return stripPrefix(pathname)\n }\n\n // ── getPathname utility ───────────────────────────────────────────────\n\n function getPathname(href: P | (string & Record<never, never>), locale: L): string {\n return resolvePath(String(href), String(locale))\n }\n\n return { Link, useRouter, redirect, usePathname, getPathname }\n}\n","'use client'\n\n/**\n * @module @fluenti/next/navigation\n *\n * Navigation utilities for locale-aware routing in Next.js App Router.\n *\n * @example\n * ```tsx\n * import { useLocaleSwitcher } from '@fluenti/next/navigation'\n *\n * function LanguagePicker() {\n * const { switchLocale, currentLocale, locales } = useLocaleSwitcher()\n * return (\n * <select value={currentLocale} onChange={(e) => switchLocale(e.target.value)}>\n * {locales.map((l) => <option key={l} value={l}>{l}</option>)}\n * </select>\n * )\n * }\n * ```\n */\nimport { useRouter, usePathname } from 'next/navigation'\nimport { useI18n } from '@fluenti/react'\nimport { cookieName as _configCookieName } from '@fluenti/next/i18n-config'\nimport { resolveInternalPath, resolveLocalizedPath } from './routing'\n\nexport interface GetLocalePathOptions {\n /** Source/default locale (no prefix in as-needed mode) */\n sourceLocale?: string\n /**\n * Known locale codes (e.g. ['en', 'fr', 'ja']).\n * When provided, the existing prefix is only stripped when it's an actual locale —\n * preventing false matches on generic 2-letter path segments like /my or /us.\n */\n locales?: string[]\n /**\n * Locale prefix strategy. Matches the middleware `localePrefix` setting.\n * - `'as-needed'` (default): source locale has no prefix (`/about` for en, `/fr/about` for fr)\n * - `'always'`: all locales get a prefix (`/en/about`, `/fr/about`)\n * - `'never'`: no locale prefix in URLs\n */\n localePrefix?: 'always' | 'as-needed' | 'never'\n}\n\n/**\n * Get the locale-prefixed path for a given pathname and locale.\n *\n * Pure function — works on both server and client.\n *\n * @example\n * ```ts\n * getLocalePath('/about', 'fr') // → '/fr/about'\n * getLocalePath('/about', 'en') // → '/about' (source locale, no prefix)\n * getLocalePath('/fr/about', 'en', { locales: ['en', 'fr', 'ja'] }) // → '/about'\n * getLocalePath('/fr/about', 'ja', { locales: ['en', 'fr', '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 // Only strip an existing locale prefix when the caller provides the known locale list.\n const hasLocalePrefix = options?.locales\n ? options.locales.some(loc => loc.toLowerCase() === firstSegment.toLowerCase())\n : false\n const pathWithoutLocale = hasLocalePrefix\n ? '/' + segments.slice(2).join('/')\n : pathname\n\n // 'never' mode: no prefix for any locale\n if (localePrefix === 'never') {\n return pathWithoutLocale || '/'\n }\n\n // In 'as-needed' mode, source locale gets no prefix\n if (localePrefix !== 'always' && locale === sourceLocale) {\n return pathWithoutLocale || '/'\n }\n\n return `/${locale}${pathWithoutLocale}`\n}\n\n/**\n * Hook for switching locales in Next.js App Router.\n *\n * Sets a cookie to remember user preference, navigates to the new locale path,\n * and triggers a server component refresh.\n */\nexport function useLocaleSwitcher(options?: {\n /** Override the source/default locale instead of inferring from locales[0]. */\n sourceLocale?: string\n /**\n * Cookie name used by the middleware for locale preference.\n * Defaults to the value from `fluenti.config.ts` (auto-read at build time).\n * Must match the middleware `cookieName` option.\n */\n cookieName?: string\n /** Locale prefix strategy — must match the middleware `localePrefix` option. */\n localePrefix?: 'always' | 'as-needed' | 'never'\n}) {\n const router = useRouter()\n const pathname = usePathname()\n const { locale, setLocale, getLocales } = useI18n()\n\n // Read locales from I18nProvider context (works on client without fs)\n const locales = getLocales()\n const sourceLocale = options?.sourceLocale ?? locales[0] ?? 'en'\n const cookieName = options?.cookieName ?? _configCookieName\n const localePrefix = options?.localePrefix ?? 'as-needed'\n\n const switchLocale = (newLocale: string) => {\n // Validate locale against known locales to prevent cookie injection\n if (!locales.includes(newLocale)) {\n if (typeof process !== 'undefined' && process.env?.['NODE_ENV'] !== 'production') {\n console.warn(`[fluenti] switchLocale: invalid locale \"${newLocale}\"`)\n }\n return\n }\n // 1. Set cookie to remember preference (uses configured cookie name)\n document.cookie = `${cookieName}=${encodeURIComponent(newLocale)};path=/;max-age=31536000;samesite=lax`\n // 2. Update React context\n setLocale(newLocale)\n // 3. Navigate to new locale path\n const newPath = getLocalePath(pathname, newLocale, { sourceLocale, locales, localePrefix })\n router.push(newPath)\n // 4. Refresh server components\n router.refresh()\n }\n\n return {\n switchLocale,\n currentLocale: locale,\n locales,\n sourceLocale,\n }\n}\n\n// Re-export createNavigation and routing utilities\nexport { createNavigation } from './create-navigation'\nexport { defineRouting, resolveLocalizedPath } from './routing'\nexport type { RoutingConfig } from './routing'\n\n// ── useAlternateLinks ─────────────────────────────────────────────────────\n\nexport interface AlternateLink {\n hreflang: string\n href: string\n}\n\n/**\n * Generate alternate link entries for SEO `<head>` tags.\n *\n * Returns an array of `{ hreflang, href }` entries for all configured locales\n * plus `x-default`. Use in `<head>` for hreflang tags.\n *\n * @example\n * ```tsx\n * import { useAlternateLinks } from '@fluenti/next/navigation'\n * import { routing } from '@/i18n/routing'\n *\n * export function Head() {\n * const links = useAlternateLinks({ routing, baseUrl: 'https://example.com' })\n * return (\n * <head>\n * {links.map(l => (\n * <link key={l.hreflang} rel=\"alternate\" hreflang={l.hreflang} href={l.href} />\n * ))}\n * </head>\n * )\n * }\n * ```\n */\nexport function useAlternateLinks(options: {\n routing: { locales: readonly string[]; sourceLocale: string; localePrefix?: 'always' | 'as-needed' | 'never'; pathnames?: Record<string, Record<string, string>> }\n baseUrl?: string\n}): AlternateLink[] {\n const { routing: r, baseUrl = '' } = options\n const pathname = usePathname()\n let activeLocale = r.sourceLocale\n try {\n activeLocale = useI18n().locale\n } catch {\n // Outside I18nProvider, fall back to sourceLocale.\n }\n // Strip locale prefix from current path\n const segments = pathname.split('/')\n const firstSeg = segments[1] ?? ''\n const pathLocale = r.localePrefix === 'never'\n ? null\n : (r.locales as string[]).find(l => l.toLowerCase() === firstSeg.toLowerCase()) ?? null\n const cleanPath = pathLocale ? '/' + segments.slice(2).join('/') || '/' : pathname\n const requestLocale = pathLocale ?? activeLocale ?? r.sourceLocale\n const internalPath = r.pathnames\n ? resolveInternalPath(cleanPath, requestLocale, r.pathnames as Record<string, Record<string, string>>) ?? cleanPath\n : cleanPath\n\n const links: AlternateLink[] = (r.locales as string[]).map(loc => {\n const localePath = r.pathnames\n ? resolveLocalizedPath(internalPath, loc, r.pathnames as Record<string, Record<string, string>>) ?? internalPath\n : internalPath\n\n let href: string\n if (r.localePrefix === 'never') {\n href = `${baseUrl}${localePath}`\n } else if (r.localePrefix !== 'always' && loc === r.sourceLocale) {\n href = `${baseUrl}${localePath}`\n } else {\n href = `${baseUrl}/${loc}${localePath}`\n }\n\n return { hreflang: loc, href }\n })\n\n // x-default\n const sourcePath = r.pathnames\n ? resolveLocalizedPath(internalPath, r.sourceLocale, r.pathnames as Record<string, Record<string, string>>) ?? internalPath\n : internalPath\n const defaultPath = r.localePrefix === 'always'\n ? `${baseUrl}/${r.sourceLocale}${sourcePath}`\n : `${baseUrl}${sourcePath}`\n links.push({ hreflang: 'x-default', href: defaultPath })\n\n return links\n}\n"],"mappings":"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"}