cloudflare-next-intl 0.9.47 → 0.9.49

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 (30) hide show
  1. package/README.md +15 -7
  2. package/bin/check_locale_params.mjs +26 -0
  3. package/dist/src/dynamic_pages_check/check_dynamic_pages.js +7 -2
  4. package/dist/src/dynamic_pages_check/detect_dynamic_usage.js +7 -0
  5. package/dist/src/dynamic_pages_check/find_page_files.js +1 -1
  6. package/dist/src/image_optimizer/plugin.js +6 -1
  7. package/dist/src/locale_params_check/check_locale_params.d.ts +24 -0
  8. package/dist/src/locale_params_check/check_locale_params.js +104 -0
  9. package/dist/src/locale_params_check/detect_locale_params.d.ts +10 -0
  10. package/dist/src/locale_params_check/detect_locale_params.js +96 -0
  11. package/dist/src/locale_params_check/find_locale_scoped_files.d.ts +1 -0
  12. package/dist/src/locale_params_check/find_locale_scoped_files.js +14 -0
  13. package/dist/src/locale_params_check/index.d.ts +4 -0
  14. package/dist/src/locale_params_check/index.js +4 -0
  15. package/dist/src/locale_params_check/insert_locale_params.d.ts +5 -0
  16. package/dist/src/locale_params_check/insert_locale_params.js +117 -0
  17. package/dist/src/vite/auto_dynamic_pages_plugin.js +2 -24
  18. package/dist/src/vite/auto_locale_params_plugin.d.ts +18 -0
  19. package/dist/src/vite/auto_locale_params_plugin.js +71 -0
  20. package/dist/src/vite/build_write_restore_stack.d.ts +1 -0
  21. package/dist/src/vite/build_write_restore_stack.js +43 -0
  22. package/dist/src/vite/index.d.ts +2 -0
  23. package/dist/src/vite/index.js +2 -0
  24. package/dist/src/vite/lucide_optimizer_plugin.d.ts +21 -0
  25. package/dist/src/vite/lucide_optimizer_plugin.js +190 -0
  26. package/dist/src/vite/plugin.d.ts +4 -0
  27. package/dist/src/vite/plugin.js +12 -0
  28. package/dist/src/vite/vinext_route_wiring_fix.js +71 -24
  29. package/llms.txt +3 -1
  30. package/package.json +7 -2
@@ -0,0 +1,71 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { checkLocaleParams } from "../locale_params_check/check_locale_params.js";
4
+ import { registerBuildWriteRestore } from "./build_write_restore_stack.js";
5
+ const RESTORABLE_ACTIONS = new Set(['added-locale-params']);
6
+ export function autoLocaleParamsPlugin(options = {}) {
7
+ let ran = false;
8
+ return {
9
+ name: "cloudflare-next-intl-auto-locale-params",
10
+ enforce: "pre",
11
+ async configResolved(config) {
12
+ if (ran)
13
+ return;
14
+ const isBuild = config.command === "build";
15
+ const isDev = config.command === "serve";
16
+ if (!isBuild && !(isDev && options.runOnDev))
17
+ return;
18
+ ran = true;
19
+ const root = config.root || process.cwd();
20
+ let appDir = options.appDir;
21
+ if (!appDir) {
22
+ if (existsSync(resolve(root, "src/app"))) {
23
+ appDir = resolve(root, "src/app");
24
+ }
25
+ else if (existsSync(resolve(root, "app"))) {
26
+ appDir = resolve(root, "app");
27
+ }
28
+ }
29
+ if (!appDir || !existsSync(appDir))
30
+ return;
31
+ const restoreAfterBuild = isBuild && (options.restoreAfterBuild ?? true);
32
+ const originals = new Map();
33
+ try {
34
+ const reports = await checkLocaleParams({
35
+ appDir,
36
+ mode: options.mode ?? "fix",
37
+ localeParam: options.localeParam,
38
+ skip: options.skip,
39
+ overrides: options.overrides,
40
+ verbose: options.verbose ?? false,
41
+ }, restoreAfterBuild
42
+ ? {
43
+ writeFile: (file, contents) => {
44
+ if (!originals.has(file)) {
45
+ try {
46
+ originals.set(file, readFileSync(file, "utf8"));
47
+ }
48
+ catch {
49
+ }
50
+ }
51
+ writeFileSync(file, contents, "utf8");
52
+ },
53
+ }
54
+ : undefined);
55
+ if (!restoreAfterBuild)
56
+ return;
57
+ const restorable = new Set(reports
58
+ .filter((report) => RESTORABLE_ACTIONS.has(report.action))
59
+ .map((report) => report.file));
60
+ for (const file of [...originals.keys()]) {
61
+ if (!restorable.has(file))
62
+ originals.delete(file);
63
+ }
64
+ registerBuildWriteRestore(originals);
65
+ }
66
+ catch (err) {
67
+ console.warn("[cloudflare-next-intl] autoLocaleParams check error:", err);
68
+ }
69
+ },
70
+ };
71
+ }
@@ -0,0 +1 @@
1
+ export declare function registerBuildWriteRestore(originals: ReadonlyMap<string, string>): void;
@@ -0,0 +1,43 @@
1
+ import { writeFileSync } from "node:fs";
2
+ const restoreStack = [];
3
+ let registered = false;
4
+ export function registerBuildWriteRestore(originals) {
5
+ if (originals.size === 0)
6
+ return;
7
+ restoreStack.push(() => {
8
+ for (const [file, contents] of originals) {
9
+ try {
10
+ writeFileSync(file, contents, "utf8");
11
+ }
12
+ catch {
13
+ }
14
+ }
15
+ });
16
+ if (registered)
17
+ return;
18
+ registered = true;
19
+ armProcessRestoreHandlers();
20
+ }
21
+ function runAllRestores() {
22
+ while (restoreStack.length > 0) {
23
+ const restore = restoreStack.pop();
24
+ restore();
25
+ }
26
+ }
27
+ function armProcessRestoreHandlers() {
28
+ process.once("exit", runAllRestores);
29
+ for (const signal of ["SIGINT", "SIGTERM"]) {
30
+ process.once(signal, () => {
31
+ runAllRestores();
32
+ process.kill(process.pid, signal);
33
+ });
34
+ }
35
+ process.once("uncaughtException", (err) => {
36
+ runAllRestores();
37
+ throw err;
38
+ });
39
+ process.once("unhandledRejection", (reason) => {
40
+ runAllRestores();
41
+ throw reason;
42
+ });
43
+ }
@@ -1,8 +1,10 @@
1
1
  export { autoDynamicPagesPlugin, type AutoDynamicPagesPluginOptions } from "./auto_dynamic_pages_plugin.js";
2
+ export { autoLocaleParamsPlugin, type AutoLocaleParamsPluginOptions } from "./auto_locale_params_plugin.js";
2
3
  export { buildIdAsset } from "./build_id_asset.js";
3
4
  export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
4
5
  export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
5
6
  export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed, type VinextRouteWiringFixPluginOptions } from "./vinext_route_wiring_fix.js";
6
7
  export { localeFilePlugin, resolveDefaultIntlConfigPath, type LocaleFilePluginOptions } from "./locale_file_plugin.js";
8
+ export { lucideOptimizerPlugin, detectLucideReact, resolveLucideEsmEntry, parseLucideIconMap, transformLucideImports, transformNextJsImports, type LucideOptimizerPluginOptions, } from "./lucide_optimizer_plugin.js";
7
9
  export { cloudflareNextIntl, cloudflareNextIntlPlugin, type CloudflareNextIntlOptions, default } from "./plugin.js";
8
10
  export { imageOptimizer, imageOptimizerPlugin, VIRTUAL_IMAGE_SHIM_ID, type ImageFormat, type ImageBlurOptions, type ImageOverrideOptions, type ImageOptimizerPluginOptions, type ResolvedBlurOptions, type ResolvedOptions, type ResolvedImageConfig, type OptimizedImage, type ManifestData, type ManifestEntry, } from "../image_optimizer/index.js";
@@ -1,8 +1,10 @@
1
1
  export { autoDynamicPagesPlugin } from "./auto_dynamic_pages_plugin.js";
2
+ export { autoLocaleParamsPlugin } from "./auto_locale_params_plugin.js";
2
3
  export { buildIdAsset } from "./build_id_asset.js";
3
4
  export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
4
5
  export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
5
6
  export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed } from "./vinext_route_wiring_fix.js";
6
7
  export { localeFilePlugin, resolveDefaultIntlConfigPath } from "./locale_file_plugin.js";
8
+ export { lucideOptimizerPlugin, detectLucideReact, resolveLucideEsmEntry, parseLucideIconMap, transformLucideImports, transformNextJsImports, } from "./lucide_optimizer_plugin.js";
7
9
  export { cloudflareNextIntl, cloudflareNextIntlPlugin, default } from "./plugin.js";
8
10
  export { imageOptimizer, imageOptimizerPlugin, VIRTUAL_IMAGE_SHIM_ID, } from "../image_optimizer/index.js";
@@ -0,0 +1,21 @@
1
+ import type { Plugin } from "vite";
2
+ export interface LucideOptimizerPluginOptions {
3
+ root?: string;
4
+ normalizeNextJsImports?: boolean;
5
+ lucideEntryPath?: string;
6
+ }
7
+ export declare const JS_EXT_RE: RegExp;
8
+ export declare const LUCIDE_IMPORT_RE: RegExp;
9
+ export declare const NEXT_JS_IMPORT_RE: RegExp;
10
+ export declare function detectLucideReact(root: string): boolean;
11
+ export declare function resolveLucideEsmEntry(root: string): string | null;
12
+ export declare function parseLucideIconMap(esmEntryPath: string): Map<string, string>;
13
+ export declare function transformLucideImports(code: string, iconMap: Map<string, string>): {
14
+ code: string;
15
+ changed: boolean;
16
+ };
17
+ export declare function transformNextJsImports(code: string): {
18
+ code: string;
19
+ changed: boolean;
20
+ };
21
+ export declare function lucideOptimizerPlugin(options?: LucideOptimizerPluginOptions): Plugin;
@@ -0,0 +1,190 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createRequire } from "node:module";
4
+ export const JS_EXT_RE = /\.[cm]?[jt]sx?$/;
5
+ export const LUCIDE_IMPORT_RE = /import\s+(?:type\s+)?\{([^}]+)\}\s+from\s*['"]lucide-react['"];?/g;
6
+ export const NEXT_JS_IMPORT_RE = /(['"])next\/([a-zA-Z0-9_-]+)\.js\1/g;
7
+ export function detectLucideReact(root) {
8
+ try {
9
+ const req = createRequire(path.join(root, "package.json"));
10
+ req.resolve("lucide-react");
11
+ return true;
12
+ }
13
+ catch {
14
+ return fs.existsSync(path.join(root, "node_modules", "lucide-react"));
15
+ }
16
+ }
17
+ export function resolveLucideEsmEntry(root) {
18
+ try {
19
+ const req = createRequire(path.join(root, "package.json"));
20
+ const pkgJsonPath = req.resolve("lucide-react/package.json");
21
+ const dir = path.dirname(pkgJsonPath);
22
+ const esm = path.join(dir, "dist", "esm", "lucide-react.mjs");
23
+ if (fs.existsSync(esm))
24
+ return esm;
25
+ }
26
+ catch {
27
+ const fallback = path.join(root, "node_modules", "lucide-react", "dist", "esm", "lucide-react.mjs");
28
+ if (fs.existsSync(fallback))
29
+ return fallback;
30
+ }
31
+ return null;
32
+ }
33
+ export function parseLucideIconMap(esmEntryPath) {
34
+ const iconMap = new Map();
35
+ try {
36
+ const content = fs.readFileSync(esmEntryPath, "utf8");
37
+ const exportRegex = /export\s*\{\s*([^}]+)\s*\}\s*from\s*['"](\.\/icons\/[^'"]+)['"];/g;
38
+ let match;
39
+ while ((match = exportRegex.exec(content)) !== null) {
40
+ const [, specifiers, file] = match;
41
+ const normalizedFile = file.replace(/^\.\//, "lucide-react/dist/esm/");
42
+ for (const part of specifiers.split(",")) {
43
+ const m = part.trim().match(/(?:(\w+)\s+as\s+)?(\w+)$/);
44
+ if (m)
45
+ iconMap.set(m[2], normalizedFile);
46
+ }
47
+ }
48
+ iconMap.set("LucideProvider", "lucide-react/dist/esm/context.mjs");
49
+ iconMap.set("useLucideContext", "lucide-react/dist/esm/context.mjs");
50
+ }
51
+ catch {
52
+ }
53
+ return iconMap;
54
+ }
55
+ export function transformLucideImports(code, iconMap) {
56
+ if (!code.includes("lucide-react") || iconMap.size === 0) {
57
+ return { code, changed: false };
58
+ }
59
+ let changed = false;
60
+ const replaced = code.replace(new RegExp(LUCIDE_IMPORT_RE), (_match, specifiersStr) => {
61
+ const clean = specifiersStr
62
+ .replace(/\/\/[^\n]*/g, "")
63
+ .replace(/\/\*[\s\S]*?\*\//g, "");
64
+ const parts = clean
65
+ .split(",")
66
+ .map((s) => s.trim())
67
+ .filter(Boolean);
68
+ const newImports = [];
69
+ const remaining = [];
70
+ for (const part of parts) {
71
+ if (part.startsWith("type ")) {
72
+ remaining.push(part);
73
+ continue;
74
+ }
75
+ const m = part.match(/^(\w+)(?:\s+as\s+(\w+))?$/);
76
+ if (m) {
77
+ const [, imported, local] = m;
78
+ const iconPath = iconMap.get(imported);
79
+ if (iconPath) {
80
+ newImports.push(`import ${local || imported} from "${iconPath}";`);
81
+ continue;
82
+ }
83
+ }
84
+ remaining.push(part);
85
+ }
86
+ if (remaining.length > 0) {
87
+ newImports.push(`import { ${remaining.join(", ")} } from "lucide-react";`);
88
+ }
89
+ return newImports.join("\n");
90
+ });
91
+ if (replaced !== code) {
92
+ changed = true;
93
+ }
94
+ return { code: replaced, changed };
95
+ }
96
+ export function transformNextJsImports(code) {
97
+ if (!code.includes("next/") || !code.includes(".js")) {
98
+ return { code, changed: false };
99
+ }
100
+ const replaced = code.replace(new RegExp(NEXT_JS_IMPORT_RE), "$1next/$2$1");
101
+ return { code: replaced, changed: replaced !== code };
102
+ }
103
+ export function lucideOptimizerPlugin(options = {}) {
104
+ let resolvedRoot = options.root ?? process.cwd();
105
+ let isLucidePresent = false;
106
+ let iconMap = new Map();
107
+ const normalizeNext = options.normalizeNextJsImports ?? true;
108
+ return {
109
+ name: "cloudflare-next-intl-lucide-optimizer",
110
+ enforce: "pre",
111
+ config(config) {
112
+ const root = config.root || resolvedRoot;
113
+ resolvedRoot = root;
114
+ isLucidePresent = detectLucideReact(root);
115
+ if (isLucidePresent) {
116
+ const entry = options.lucideEntryPath ?? resolveLucideEsmEntry(root);
117
+ if (entry) {
118
+ iconMap = parseLucideIconMap(entry);
119
+ }
120
+ }
121
+ const vinextHeadersShim = path.join(root, "node_modules", "vinext", "dist", "shims", "headers.js");
122
+ const hasVinextHeaders = fs.existsSync(vinextHeadersShim);
123
+ const aliasEntries = [];
124
+ if (hasVinextHeaders) {
125
+ aliasEntries.push({
126
+ find: /^next\/headers(\.js)?$/,
127
+ replacement: vinextHeadersShim,
128
+ });
129
+ }
130
+ if (normalizeNext) {
131
+ aliasEntries.push({
132
+ find: /^next\/(.+)\.js$/,
133
+ replacement: "next/$1",
134
+ });
135
+ }
136
+ return {
137
+ resolve: {
138
+ alias: aliasEntries,
139
+ },
140
+ optimizeDeps: {
141
+ include: [
142
+ "next/dynamic",
143
+ "next/link",
144
+ "next/navigation",
145
+ "next/image",
146
+ ],
147
+ exclude: [
148
+ "next/server",
149
+ "next/headers",
150
+ ...(isLucidePresent ? ["lucide-react"] : []),
151
+ ],
152
+ },
153
+ };
154
+ },
155
+ configResolved(config) {
156
+ if (config.root) {
157
+ resolvedRoot = config.root;
158
+ }
159
+ },
160
+ transform(code, id) {
161
+ const cleanId = id.split("?")[0];
162
+ if (!JS_EXT_RE.test(cleanId) || cleanId.includes("node_modules/lucide-react")) {
163
+ return null;
164
+ }
165
+ if (cleanId.includes("node_modules") && !cleanId.includes("cloudflare-next-intl")) {
166
+ return null;
167
+ }
168
+ let transformed = code;
169
+ let hasChange = false;
170
+ if (isLucidePresent && iconMap.size > 0) {
171
+ const res = transformLucideImports(transformed, iconMap);
172
+ if (res.changed) {
173
+ transformed = res.code;
174
+ hasChange = true;
175
+ }
176
+ }
177
+ if (normalizeNext) {
178
+ const res = transformNextJsImports(transformed);
179
+ if (res.changed) {
180
+ transformed = res.code;
181
+ hasChange = true;
182
+ }
183
+ }
184
+ if (!hasChange) {
185
+ return null;
186
+ }
187
+ return { code: transformed, map: null };
188
+ },
189
+ };
190
+ }
@@ -2,8 +2,11 @@ import type { Plugin } from "vite";
2
2
  import { type LocaleFilePluginOptions } from "./locale_file_plugin.js";
3
3
  import { type ImageOptimizerPluginOptions } from "../image_optimizer/index.js";
4
4
  import { type AutoDynamicPagesPluginOptions } from "./auto_dynamic_pages_plugin.js";
5
+ import { type AutoLocaleParamsPluginOptions } from "./auto_locale_params_plugin.js";
5
6
  import { type VinextRouteWiringFixPluginOptions } from "./vinext_route_wiring_fix.js";
7
+ import { type LucideOptimizerPluginOptions } from "./lucide_optimizer_plugin.js";
6
8
  export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
9
+ lucideOptimizer?: boolean | LucideOptimizerPluginOptions;
7
10
  autoDynamicPages?: boolean | AutoDynamicPagesPluginOptions;
8
11
  buildIdAsset?: boolean | string;
9
12
  localeFiles?: boolean;
@@ -11,6 +14,7 @@ export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
11
14
  cfWorkersClientStub?: boolean;
12
15
  imageOptimizer?: boolean | ImageOptimizerPluginOptions;
13
16
  vinextRouteWiringFix?: boolean | VinextRouteWiringFixPluginOptions;
17
+ autoLocaleParams?: boolean | AutoLocaleParamsPluginOptions;
14
18
  }
15
19
  export declare function cloudflareNextIntl(options?: CloudflareNextIntlOptions): Plugin[];
16
20
  export declare const cloudflareNextIntlPlugin: typeof cloudflareNextIntl;
@@ -4,9 +4,21 @@ import { cfWorkersClientStubPlugin } from "./cf_workers_client_stub.js";
4
4
  import { localeFilePlugin } from "./locale_file_plugin.js";
5
5
  import { imageOptimizerPlugin } from "../image_optimizer/index.js";
6
6
  import { autoDynamicPagesPlugin } from "./auto_dynamic_pages_plugin.js";
7
+ import { autoLocaleParamsPlugin } from "./auto_locale_params_plugin.js";
7
8
  import { vinextRouteWiringFixPlugin } from "./vinext_route_wiring_fix.js";
9
+ import { lucideOptimizerPlugin } from "./lucide_optimizer_plugin.js";
8
10
  export function cloudflareNextIntl(options = {}) {
9
11
  const plugins = [];
12
+ if (options.lucideOptimizer !== false) {
13
+ plugins.push(lucideOptimizerPlugin(typeof options.lucideOptimizer === "object"
14
+ ? options.lucideOptimizer
15
+ : { root: options.root }));
16
+ }
17
+ if (options.autoLocaleParams !== false) {
18
+ plugins.push(autoLocaleParamsPlugin(typeof options.autoLocaleParams === "object"
19
+ ? options.autoLocaleParams
20
+ : undefined));
21
+ }
10
22
  if (options.autoDynamicPages !== false) {
11
23
  plugins.push(autoDynamicPagesPlugin(typeof options.autoDynamicPages === "object"
12
24
  ? options.autoDynamicPages
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, writeFileSync, rmSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
- const PREFETCH_LOADING_FN_RE = /function\s+getPrefetchLoadingEntry\s*\(\s*route\s*\)\s*\{[\s\S]*?return\s+getDefaultExport\s*\(\s*route\.loading\s*\)\s*\?[\s\S]*?:\s*null\s*;\s*\}/;
3
+ const PREFETCH_LOADING_FN_RE = /function\s+getPrefetchLoadingEntry\s*\(\s*route\s*\)\s*\{[\s\S]*?firstNestedEntry[\s\S]*?route\.loadings[\s\S]*?return\s+getDefaultExport\s*\(\s*route\.loading\s*\)\s*\?[\s\S]*?:\s*null\s*;\s*\}/;
4
4
  const FIXED_PREFETCH_LOADING_FN = `function getPrefetchLoadingEntry(route) {
5
5
  let rootEntry = null;
6
6
  let deepestNestedEntry = null;
@@ -29,7 +29,8 @@ const FIXED_PREFETCH_LOADING_FN = `function getPrefetchLoadingEntry(route) {
29
29
  const ROUTE_LOADING_GUARD_RE = /if\s*\(\s*!isPrefetchLoadingShell\s*&&\s*treePosition\s*<\s*routeSegments\.length\s*\)\s*\{/g;
30
30
  const FIXED_ROUTE_LOADING_GUARD = "if (!isPrefetchLoadingShell && treePosition < routeSegments.length && !routeLoadingComponent) {";
31
31
  export function isAppPageRouteWiringAlreadyFixed(code) {
32
- const hasBuggyPrefetch = code.includes("firstNestedEntry") && PREFETCH_LOADING_FN_RE.test(code);
32
+ const hasBuggyPrefetch = code.includes("firstNestedEntry") &&
33
+ PREFETCH_LOADING_FN_RE.test(code);
33
34
  const hasBuggySuspense = !code.includes("!routeLoadingComponent") && ROUTE_LOADING_GUARD_RE.test(code);
34
35
  return !hasBuggyPrefetch && !hasBuggySuspense;
35
36
  }
@@ -38,8 +39,10 @@ export function patchAppPageRouteWiring(code) {
38
39
  return code;
39
40
  }
40
41
  let result = code;
41
- const hasBuggyPrefetch = code.includes("firstNestedEntry") && PREFETCH_LOADING_FN_RE.test(code);
42
- if (hasBuggyPrefetch && !result.includes("deepestNestedEntry")) {
42
+ const hasBuggyPrefetch = code.includes("firstNestedEntry") &&
43
+ !result.includes("deepestNestedEntry") &&
44
+ PREFETCH_LOADING_FN_RE.test(result);
45
+ if (hasBuggyPrefetch) {
43
46
  result = result.replace(PREFETCH_LOADING_FN_RE, FIXED_PREFETCH_LOADING_FN);
44
47
  }
45
48
  const hasBuggySuspense = !result.includes("!routeLoadingComponent") && ROUTE_LOADING_GUARD_RE.test(result);
@@ -53,13 +56,20 @@ export function isRouteMatchingFile(id) {
53
56
  return cleanId.endsWith("/routing/route-matching.js") || cleanId.endsWith("/routing/route-matching.ts");
54
57
  }
55
58
  export function isRouteMatchingAlreadyFixed(code) {
56
- return code.includes("hasLeadingLocaleParam");
59
+ if (code.includes("hasLeadingLocaleParam")) {
60
+ return true;
61
+ }
62
+ const hasUpstreamLocaleMatch = code.includes(":locale") &&
63
+ (code.includes("activeLocale") || code.includes("getActiveRouteLocale") || code.includes("matchWithLocale") || code.includes("localeMatch")) &&
64
+ /function\s+matchRouteWithTrie[\s\S]*?:locale/.test(code);
65
+ return Boolean(hasUpstreamLocaleMatch);
57
66
  }
58
- const MATCH_ROUTE_WITH_TRIE_RE = /function\s+matchRouteWithTrie\s*\(\s*url\s*,\s*routes\s*,\s*cache\s*\)\s*\{[\s\S]*?return\s+trieMatch\([\s\S]*?\);\s*\}/;
59
- const FIXED_MATCH_ROUTE_WITH_TRIE = `function getActiveRouteLocale() {
67
+ const GET_ACTIVE_ROUTE_LOCALE_FN = `function getActiveRouteLocale() {
60
68
  return (typeof document !== "undefined" && (document.documentElement?.lang || document.cookie.match(/__user_locale_key__=([^;]+)/)?.[1])) || (typeof window !== "undefined" && window.__VINEXT_LOCALE__) || "en";
61
69
  }
62
- function matchRouteWithTrie(url, routes, cache) {
70
+ `;
71
+ const MATCH_ROUTE_WITH_TRIE_RE = /function\s+matchRouteWithTrie\s*\(\s*url\s*,\s*routes\s*,\s*cache\s*\)\s*\{[\s\S]*?normalizePathnameForRouteMatch[\s\S]*?getOrBuildTrie\(\s*cache\s*,\s*routes\s*\)\s*;?\s*\n\s*return\s+trieMatch\(\s*trie\s*,\s*urlParts\s*\)\s*;?\s*\}/;
72
+ const FIXED_MATCH_ROUTE_WITH_TRIE_BODY = `function matchRouteWithTrie(url, routes, cache) {
63
73
  const pathname = url.split("?")[0];
64
74
  let normalizedUrl = pathname === "/" ? "/" : pathname.replace(/\\/$/, "");
65
75
  normalizedUrl = normalizePathnameForRouteMatch(normalizedUrl);
@@ -75,7 +85,7 @@ function matchRouteWithTrie(url, routes, cache) {
75
85
  }
76
86
  return trieMatch(trie, urlParts);
77
87
  }`;
78
- const MATCH_ROUTE_WITH_TRIE_RAW_RE = /function\s+matchRouteWithTrieRawPathname\s*\(\s*url\s*,\s*routes\s*,\s*cache\s*\)\s*\{[\s\S]*?return\s+trieMatch\([\s\S]*?\);\s*\}/;
88
+ const MATCH_ROUTE_WITH_TRIE_RAW_RE = /function\s+matchRouteWithTrieRawPathname\s*\(\s*url\s*,\s*routes\s*,\s*cache\s*\)\s*\{[\s\S]*?urlParts\s*=[\s\S]*?return\s+trieMatch\(\s*(?:trie|getOrBuildTrie\(\s*cache\s*,\s*routes\s*\))\s*,\s*urlParts\s*\)\s*;?\s*\}/;
79
89
  const FIXED_MATCH_ROUTE_WITH_TRIE_RAW = `function matchRouteWithTrieRawPathname(url, routes, cache) {
80
90
  const pathname = url.split("?")[0];
81
91
  const urlParts = (pathname === "/" ? "/" : pathname.replace(/\\/$/, "")).split("/").filter(Boolean);
@@ -95,11 +105,17 @@ export function patchRouteMatching(code) {
95
105
  return code;
96
106
  }
97
107
  let result = code;
98
- if (MATCH_ROUTE_WITH_TRIE_RE.test(result)) {
99
- result = result.replace(MATCH_ROUTE_WITH_TRIE_RE, FIXED_MATCH_ROUTE_WITH_TRIE);
108
+ if (!result.includes("hasLeadingLocaleParam") && MATCH_ROUTE_WITH_TRIE_RE.test(result)) {
109
+ const replacement = result.includes("function getActiveRouteLocale")
110
+ ? FIXED_MATCH_ROUTE_WITH_TRIE_BODY
111
+ : `${GET_ACTIVE_ROUTE_LOCALE_FN}${FIXED_MATCH_ROUTE_WITH_TRIE_BODY}`;
112
+ result = result.replace(MATCH_ROUTE_WITH_TRIE_RE, replacement);
100
113
  }
101
- if (MATCH_ROUTE_WITH_TRIE_RAW_RE.test(result)) {
102
- result = result.replace(MATCH_ROUTE_WITH_TRIE_RAW_RE, FIXED_MATCH_ROUTE_WITH_TRIE_RAW);
114
+ if (!result.includes("hasLeadingLocaleParam") && MATCH_ROUTE_WITH_TRIE_RAW_RE.test(result)) {
115
+ const replacement = result.includes("function getActiveRouteLocale")
116
+ ? FIXED_MATCH_ROUTE_WITH_TRIE_RAW
117
+ : `${GET_ACTIVE_ROUTE_LOCALE_FN}${FIXED_MATCH_ROUTE_WITH_TRIE_RAW}`;
118
+ result = result.replace(MATCH_ROUTE_WITH_TRIE_RAW_RE, replacement);
103
119
  }
104
120
  return result;
105
121
  }
@@ -111,13 +127,17 @@ export function isOptimisticRoutingAlreadyFixed(code) {
111
127
  const hasLocalePrefixFirst = code.includes("hasLeadingLocaleParam") &&
112
128
  code.indexOf("hasLeadingLocaleParam") < code.indexOf("const match = matchNode(trie, urlParts.normalized");
113
129
  const hasRawPartsFix = code.includes("options.rawUrlParts[0] !== options.match.params.locale");
114
- return hasLocalePrefixFirst && hasRawPartsFix;
115
- }
116
- const MATCH_OPTIMISTIC_ROUTE_RE = /function\s+matchOptimisticRouteManifestRoute\s*\(\s*options\s*\)\s*\{[\s\S]*?getRouteTrie\([\s\S]*?\)[\s\S]*?\n\}/;
117
- const FIXED_MATCH_OPTIMISTIC_ROUTE = `function getActiveRouteLocale() {
118
- return (typeof document !== "undefined" && (document.documentElement?.lang || document.cookie.match(/__user_locale_key__=([^;]+)/)?.[1])) || (typeof window !== "undefined" && window.__VINEXT_LOCALE__) || "en";
130
+ if (hasLocalePrefixFirst && hasRawPartsFix) {
131
+ return true;
132
+ }
133
+ const hasUpstreamLocaleMatch = code.includes(":locale") &&
134
+ (code.includes("activeLocale") || code.includes("getActiveRouteLocale") || code.includes("localeMatch")) &&
135
+ /function\s+matchOptimisticRouteManifestRoute[\s\S]*?:locale/.test(code);
136
+ const hasUpstreamRawPartsFix = /function\s+resolveOptimisticNavigationParams[\s\S]*?:locale/.test(code);
137
+ return Boolean(hasUpstreamLocaleMatch && hasUpstreamRawPartsFix);
119
138
  }
120
- function matchOptimisticRouteManifestRoute(options) {
139
+ const MATCH_OPTIMISTIC_ROUTE_RE = /function\s+matchOptimisticRouteManifestRoute\s*\(\s*options\s*\)\s*\{[\s\S]*?hrefToRouteParts\(\s*options\.href\s*,\s*options\.basePath\s*\)[\s\S]*?matchNode\([\s\S]*?urlParts\.normalized\s*,\s*0\s*,\s*\[\]\)[\s\S]*?decodeMatchedParams\(\s*match\.params\s*\)[\s\S]*?\n\}/;
140
+ const FIXED_MATCH_OPTIMISTIC_ROUTE_BODY = `function matchOptimisticRouteManifestRoute(options) {
121
141
  const urlParts = hrefToRouteParts(options.href, options.basePath);
122
142
  if (urlParts === null) return null;
123
143
  const trie = getRouteTrie(options.routeManifest);
@@ -151,7 +171,10 @@ export function patchOptimisticRouting(code) {
151
171
  }
152
172
  let result = code;
153
173
  if (!result.includes("hasLeadingLocaleParam") && MATCH_OPTIMISTIC_ROUTE_RE.test(result)) {
154
- result = result.replace(MATCH_OPTIMISTIC_ROUTE_RE, FIXED_MATCH_OPTIMISTIC_ROUTE);
174
+ const replacement = result.includes("function getActiveRouteLocale")
175
+ ? FIXED_MATCH_OPTIMISTIC_ROUTE_BODY
176
+ : `${GET_ACTIVE_ROUTE_LOCALE_FN}${FIXED_MATCH_OPTIMISTIC_ROUTE_BODY}`;
177
+ result = result.replace(MATCH_OPTIMISTIC_ROUTE_RE, replacement);
155
178
  }
156
179
  if (!result.includes("options.rawUrlParts[0] !== options.match.params.locale") && RESOLVE_OPTIMISTIC_NAV_PARAMS_RE.test(result)) {
157
180
  result = result.replace(RESOLVE_OPTIMISTIC_NAV_PARAMS_RE, FIXED_RESOLVE_OPTIMISTIC_NAV_PARAMS);
@@ -165,9 +188,16 @@ export function isPrefetchLearningFile(id) {
165
188
  export function isPrefetchLearningAlreadyFixed(code) {
166
189
  const hasFixedFunction = code.includes("stripRsc(parsePrefetchCacheKey(cacheKey).rscUrl)") && code.includes("hasOptimisticTemplate");
167
190
  const hasFixedCallSite = code.includes("targetHref: currentHref,") && code.includes("targetRscUrl: rscUrl,") && !LEARN_TEMPLATES_CALL_RE.test(code);
168
- return hasFixedFunction && hasFixedCallSite;
191
+ if (hasFixedFunction && hasFixedCallSite) {
192
+ return true;
193
+ }
194
+ const hasBuggySkip = /if\s*\(\s*!isSettledPrefetchCacheEntry\(\s*entry\s*\)\s*\)\s*continue\s*;/.test(code);
195
+ if (!hasBuggySkip && code.includes("learnOptimisticRouteTemplatesFromPrefetchCache")) {
196
+ return true;
197
+ }
198
+ return false;
169
199
  }
170
- const LEARN_TEMPLATES_FN_RE = /async\s+function\s+learnOptimisticRouteTemplatesFromPrefetchCache\s*\(\s*options\s*\)\s*\{[\s\S]*?await\s+Promise\.allSettled\(\s*learning\s*\);\s*\}/;
200
+ const LEARN_TEMPLATES_FN_RE = /async\s+function\s+learnOptimisticRouteTemplatesFromPrefetchCache\s*\(\s*options\s*\)\s*\{[\s\S]*?getPrefetchCache\(\)[\s\S]*?if\s*\(\s*!isSettledPrefetchCacheEntry\(\s*entry\s*\)\s*\)\s*continue\s*;[\s\S]*?learnOptimisticRouteTemplateFromPrefetch[\s\S]*?await\s+Promise\.allSettled\(\s*learning\s*\)\s*;?\s*\}/;
171
201
  const FIXED_LEARN_TEMPLATES_FN = `async function learnOptimisticRouteTemplatesFromPrefetchCache(options) {
172
202
  if (options.routeManifest === null) return;
173
203
  const hasOptimisticTemplate = options.targetHref !== void 0 && resolveOptimisticNavigationPayload({
@@ -241,10 +271,15 @@ export function patchPrefetchLearning(code) {
241
271
  return code;
242
272
  }
243
273
  let result = code;
244
- if (!result.includes("hasOptimisticTemplate") && LEARN_TEMPLATES_FN_RE.test(result)) {
274
+ const hasBuggyFn = !result.includes("hasOptimisticTemplate") &&
275
+ !result.includes("isPendingNavigationTarget") &&
276
+ LEARN_TEMPLATES_FN_RE.test(result);
277
+ const hasFixedFunction = result.includes("stripRsc(parsePrefetchCacheKey(cacheKey).rscUrl)") &&
278
+ result.includes("hasOptimisticTemplate");
279
+ if (hasBuggyFn) {
245
280
  result = result.replace(LEARN_TEMPLATES_FN_RE, FIXED_LEARN_TEMPLATES_FN);
246
281
  }
247
- if (LEARN_TEMPLATES_CALL_RE.test(result)) {
282
+ if ((hasBuggyFn || hasFixedFunction) && LEARN_TEMPLATES_CALL_RE.test(result)) {
248
283
  result = result.replace(LEARN_TEMPLATES_CALL_RE, FIXED_LEARN_TEMPLATES_CALL);
249
284
  }
250
285
  return result;
@@ -386,6 +421,9 @@ export function vinextRouteWiringFixPlugin(options = {}) {
386
421
  return;
387
422
  }
388
423
  const patched = patchAppPageRouteWiring(code);
424
+ if (patched === code) {
425
+ return;
426
+ }
389
427
  return {
390
428
  code: patched,
391
429
  map: null,
@@ -396,6 +434,9 @@ export function vinextRouteWiringFixPlugin(options = {}) {
396
434
  return;
397
435
  }
398
436
  const patched = patchRouteMatching(code);
437
+ if (patched === code) {
438
+ return;
439
+ }
399
440
  return {
400
441
  code: patched,
401
442
  map: null,
@@ -406,6 +447,9 @@ export function vinextRouteWiringFixPlugin(options = {}) {
406
447
  return;
407
448
  }
408
449
  const patched = patchPrefetchLearning(code);
450
+ if (patched === code) {
451
+ return;
452
+ }
409
453
  return {
410
454
  code: patched,
411
455
  map: null,
@@ -416,6 +460,9 @@ export function vinextRouteWiringFixPlugin(options = {}) {
416
460
  return;
417
461
  }
418
462
  const patched = patchOptimisticRouting(code);
463
+ if (patched === code) {
464
+ return;
465
+ }
419
466
  return {
420
467
  code: patched,
421
468
  map: null,
package/llms.txt CHANGED
@@ -27,7 +27,9 @@ other subpath can be used.
27
27
  - `./db` — `withPublicDb(fn)` / `withUserDb(fn, uid?)` server-side Postgres/Drizzle context helpers (require `db` set on your `RoutingConfig`; direct Postgres or Supabase Data API with automatic PostgREST REST translation and `cfni_exec` fallback, see below).
28
28
  - `./dbEslint` — flat-config ESLint fragment banning direct `@supabase/supabase-js`, `pg`, `postgres`, and deep `dist/` imports in application code.
29
29
  - `./dbHelpers` — generic Drizzle SQL helper functions (`excluded`, `onConflictSet`, `ago`, `currentDate`, `windowCount`, `unnestLateral`, `ascNullsLast`, `alwaysTrue`, `lateral`, `aliasColumn`, `minOf`, `maxOf`, `roundReal`, `multiply`, `scalarFromCte`) for use with `./db`.
30
- - `./vite` — `cloudflareNextIntl(options?)` / `cloudflareNextIntlPlugin`, `imageOptimizerPlugin(options?)` / `imageOptimizer`, `buildIdAsset(fileName?)`, `localeFilePlugin(options?)`, `userAgentStubPlugin()`, `cfWorkersClientStubPlugin()`, `vinextRouteWiringFixPlugin(options?)`: All-in-one Vite plugin required for Vinext/Cloudflare Workers environments (bundles `@locale-file/*` via eager glob, resolves `@intl-config`, stubs Node.js `user-agent` to prevent runtime `node:fs` errors, stubs `cloudflare:workers` for client builds, emits client `BUILD_ID`, runs build-time/dev Image Optimizer with Next.js blur placeholder shimming, and fixes vinext route wiring, matching, and non-blocking optimistic prefetch learning).
30
+ - `./vite` — `cloudflareNextIntl(options?)` / `cloudflareNextIntlPlugin`, `imageOptimizerPlugin(options?)` / `imageOptimizer`, `autoLocaleParamsPlugin(options?)`, `buildIdAsset(fileName?)`, `localeFilePlugin(options?)`, `userAgentStubPlugin()`, `cfWorkersClientStubPlugin()`, `vinextRouteWiringFixPlugin(options?)`, `lucideOptimizerPlugin(options?)`: All-in-one Vite plugin required for Vinext/Cloudflare Workers environments (bundles `@locale-file/*` via eager glob, resolves `@intl-config`, stubs Node.js `user-agent` to prevent runtime `node:fs` errors, stubs `cloudflare:workers` for client builds, emits client `BUILD_ID`, runs build-time/dev Image Optimizer with Next.js blur placeholder shimming, fixes vinext route wiring/matching/non-blocking optimistic prefetch learning, auto-inserts locale params for pages reading locale from cookies, and rewrites `lucide-react` imports to deep icon paths). Also wraps `checkDynamicPages`/`checkLocaleParams` (see `./checkDynamicPages` / `./checkLocaleParams`), running `autoLocaleParams` before `autoDynamicPages` so a page whose locale param this fixes no longer trips the cookie-derived-locale dynamic signal.
31
+ - `./checkDynamicPages` — `checkDynamicPages(options, io?)`: standalone SSG-eligibility scanner/fixer for `page`/`route` files (text-heuristic, transitive local-import following, capped/cycle-safe). Modes `"off" | "report" | "fix"`. Also usable as the `cfni-check-dynamic-pages` CLI bin.
32
+ - `./checkLocaleParams` — `checkLocaleParams(options, io?)`: standalone scanner/fixer for `page`/`layout`/`loading` files under a `[locale]`-scoped route that call `getTranslations()`/`useTranslations()` without a `locale` param or `setLocale()` call (the source of a hidden cookie-derived-locale dependency). Modes `"off" | "report" | "fix"`; options include `appDir`, `localeParam` (default `"locale"`), `skip`, `overrides`. Also usable as the `cfni-check-locale-params` CLI bin.
31
33
  - `./image-optimizer` / `./imageOptimizer` — image optimization suite: `imageOptimizerPlugin`, `imageOptimizer`, `resolveOptions`, `resolveImageConfig`, `resolveBlurOptions`, `processImage`, `makeBlurDataURL`, `getImageBlurSvg`, `renderManifest`, `writeManifest`, `isFresh`, `loadCache`, `saveCache`, `collectImages`, `run`.
32
34
  - `./errorHandling` — error reporting & stale deploy recovery barrel: `reportError`, `withErrorHandling`, `installConsoleErrorOverride`, `installGlobalErrorOverride`, `stringifyUnknown`, `formatErrorMessage`, `defaultIgnoredConsoleErrors`, `createServerErrorAction`, `isStaleDeployError`, `defaultStaleDeployPatterns`, `setStaleDeployPatterns`, `getStaleDeployPatterns`, `clearClientCache`, `useStaleDeployRecovery`, `shouldRecoverFromStaleDeploy`, `isRecentBuild`.
33
35
  - `./isStaleDeployError` — `isStaleDeployError(error, patterns?)`, `setStaleDeployPatterns(patterns)`, `getStaleDeployPatterns()`: detector returning `true` for version skew / chunk load / dynamic import / server action 404 / hydration errors (ChunkLoadError, UnrecognizedActionError, server action not found, failed to fetch, dynamically imported module failure, loading CSS chunk, connection closed, RSC payload failure, minified error #412, or missing stream error `undefined`) with fast pre-lowercased pattern cache and intl-config integration (`errorHandling.staleDeployPatterns`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.9.47",
3
+ "version": "0.9.49",
4
4
  "description": "Optimized Next Intl Package Special for App Router and Cloudflare",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,8 @@
11
11
  "cfni-db-install-exec": "bin/db_install_exec.mjs",
12
12
  "cfni-image-optimizer": "bin/image_optimizer.mjs",
13
13
  "optimize-images": "bin/image_optimizer.mjs",
14
- "cfni-check-dynamic-pages": "bin/check_dynamic_pages.mjs"
14
+ "cfni-check-dynamic-pages": "bin/check_dynamic_pages.mjs",
15
+ "cfni-check-locale-params": "bin/check_locale_params.mjs"
15
16
  },
16
17
  "files": [
17
18
  "dist",
@@ -238,6 +239,10 @@
238
239
  "types": "./dist/src/dynamic_pages_check/index.d.ts",
239
240
  "import": "./dist/src/dynamic_pages_check/index.js"
240
241
  },
242
+ "./checkLocaleParams": {
243
+ "types": "./dist/src/locale_params_check/index.d.ts",
244
+ "import": "./dist/src/locale_params_check/index.js"
245
+ },
241
246
  "./db": {
242
247
  "types": "./dist/src/db/index.d.ts",
243
248
  "import": "./dist/src/db/index.js"