cloudflare-next-intl 0.10.26 → 0.10.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/src/client_messages_scan/index.d.ts +3 -0
- package/dist/src/client_messages_scan/index.js +3 -0
- package/dist/src/client_messages_scan/match_namespace.d.ts +1 -0
- package/dist/src/client_messages_scan/match_namespace.js +3 -0
- package/dist/src/client_messages_scan/scan_file.d.ts +16 -0
- package/dist/src/client_messages_scan/scan_file.js +175 -0
- package/dist/src/client_messages_scan/scan_project.d.ts +16 -0
- package/dist/src/client_messages_scan/scan_project.js +67 -0
- package/dist/src/cookie_consent/client/components/auto_analytics_events.js +1 -1
- package/dist/src/cookie_consent/client/components/cookie_consent_analytics.js +15 -2
- package/dist/src/cookie_consent/client/components/cookie_consent_dialog.js +4 -1
- package/dist/src/cookie_consent/client/components/privacy_policy_update_dialog.js +4 -1
- package/dist/src/cookie_consent/index.d.ts +1 -0
- package/dist/src/cookie_consent/index.js +1 -0
- package/dist/src/general/pick_client_messages.d.ts +2 -0
- package/dist/src/general/pick_client_messages.js +17 -0
- package/dist/src/image_optimizer/next_image_shim.js +10 -1
- package/dist/src/server/components/server_provider.js +2 -1
- package/dist/src/server/components/server_provider_static.js +2 -1
- package/dist/src/types/types.d.ts +1 -0
- package/dist/src/vite/auto_client_messages_plugin.d.ts +7 -0
- package/dist/src/vite/auto_client_messages_plugin.js +34 -0
- package/dist/src/vite/auto_image_loader_plugin.d.ts +21 -0
- package/dist/src/vite/auto_image_loader_plugin.js +151 -0
- package/dist/src/vite/index.d.ts +2 -0
- package/dist/src/vite/index.js +2 -0
- package/dist/src/vite/plugin.d.ts +4 -0
- package/dist/src/vite/plugin.js +12 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -166,6 +166,22 @@ export function Navigation() {
|
|
|
166
166
|
}
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
+
`IntlProvider` sends the locale's messages to the client for `useTranslations`
|
|
170
|
+
in client components. Set `clientMessages` in your config to shrink that
|
|
171
|
+
payload: `false` sends none (server-only translations), an array such as
|
|
172
|
+
`["Auth", "Common", "Categories*"]` sends only matching namespaces, or `"auto"`
|
|
173
|
+
uses build-time AST scan via the `autoClientMessages` Vite plugin. Default: `true`.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
// vite.config.ts
|
|
177
|
+
cloudflareNextIntl({
|
|
178
|
+
autoClientMessages: { fallbackNamespaces: ["Common"], onDynamic: "fallback", packages: ["@my/ui"] }
|
|
179
|
+
})
|
|
180
|
+
// intl.config.ts -> setIntlConfig({ clientMessages: "auto", ... })
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
> UI library packages can declare `"i18n": { "namespaces": ["Buttons"] }` in `package.json` for automatic inclusion.
|
|
184
|
+
|
|
169
185
|
### Locale-aware links (non-locale-switching)
|
|
170
186
|
|
|
171
187
|
Use `Link` (server-safe, from `./Link`) for normal navigation that should
|
|
@@ -609,6 +625,9 @@ import { useCookieConsent } from "cloudflare-next-intl/useCookieConsent";
|
|
|
609
625
|
const { consent, setConsent } = useCookieConsent();
|
|
610
626
|
```
|
|
611
627
|
|
|
628
|
+
Composing the providers yourself (`autoWireAnalytics: false`)? `AutoAnalyticsEvents`
|
|
629
|
+
is exported from `cloudflare-next-intl/AutoAnalyticsEvents` (and `cloudflare-next-intl/cookieConsent`).
|
|
630
|
+
|
|
612
631
|
See [`package/src/cookie_consent/README.md`](package/src/cookie_consent/README.md) for layout, customization, and gotchas.
|
|
613
632
|
|
|
614
633
|
### Error handling
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function matchesNamespacePattern(ns: string, pattern: string): boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface DynamicCall {
|
|
2
|
+
file: string;
|
|
3
|
+
line: number;
|
|
4
|
+
text: string;
|
|
5
|
+
prop?: {
|
|
6
|
+
component: string;
|
|
7
|
+
name: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface FileScanResult {
|
|
11
|
+
namespaces: string[];
|
|
12
|
+
dynamicCalls: DynamicCall[];
|
|
13
|
+
usesRoot: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function findJsxPropValues(file: string, code: string, component: string, prop: string): string[] | null;
|
|
16
|
+
export declare function scanFile(file: string, code: string): FileScanResult;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
const HOOK_MODULES = new Set(["cloudflare-next-intl/use", "cloudflare-next-intl/client", "cloudflare-next-intl"]);
|
|
3
|
+
function topLevel(ns) {
|
|
4
|
+
return ns.split(".")[0];
|
|
5
|
+
}
|
|
6
|
+
function literalsOfType(node, sf, aliases) {
|
|
7
|
+
if (!node)
|
|
8
|
+
return null;
|
|
9
|
+
if (ts.isLiteralTypeNode(node) && ts.isStringLiteral(node.literal))
|
|
10
|
+
return [node.literal.text];
|
|
11
|
+
if (ts.isUnionTypeNode(node)) {
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const t of node.types) {
|
|
14
|
+
const r = literalsOfType(t, sf, aliases);
|
|
15
|
+
if (!r)
|
|
16
|
+
return null;
|
|
17
|
+
out.push(...r);
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName))
|
|
22
|
+
return literalsOfType(aliases.get(node.typeName.text), sf, aliases);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
function componentName(fn) {
|
|
26
|
+
if ((ts.isFunctionDeclaration(fn) || ts.isFunctionExpression(fn)) && fn.name)
|
|
27
|
+
return fn.name.text;
|
|
28
|
+
if ((ts.isArrowFunction(fn) || ts.isFunctionExpression(fn)) && ts.isVariableDeclaration(fn.parent) && ts.isIdentifier(fn.parent.name))
|
|
29
|
+
return fn.parent.name.text;
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function propOf(arg, propParams) {
|
|
33
|
+
if (ts.isIdentifier(arg)) {
|
|
34
|
+
const p = propParams.get(arg.text);
|
|
35
|
+
return p ?? null;
|
|
36
|
+
}
|
|
37
|
+
if (ts.isTemplateExpression(arg) && arg.head.text === "" && ts.isIdentifier(arg.templateSpans[0].expression)) {
|
|
38
|
+
const p = propParams.get(arg.templateSpans[0].expression.text);
|
|
39
|
+
return p ?? null;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
export function findJsxPropValues(file, code, component, prop) {
|
|
44
|
+
if (!code.includes(`<${component}`))
|
|
45
|
+
return [];
|
|
46
|
+
const sf = ts.createSourceFile(file, code, ts.ScriptTarget.Latest, true, file.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
47
|
+
const values = [];
|
|
48
|
+
let unresolved = false;
|
|
49
|
+
const visit = (node) => {
|
|
50
|
+
if ((ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) && node.tagName.getText() === component) {
|
|
51
|
+
let value = null;
|
|
52
|
+
for (const attr of node.attributes.properties) {
|
|
53
|
+
if (ts.isJsxSpreadAttribute(attr)) {
|
|
54
|
+
value = null;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
if (attr.name.getText() !== prop)
|
|
58
|
+
continue;
|
|
59
|
+
const init = attr.initializer;
|
|
60
|
+
if (init && ts.isStringLiteral(init))
|
|
61
|
+
value = init.text;
|
|
62
|
+
else if (init && ts.isJsxExpression(init) && init.expression && (ts.isStringLiteral(init.expression) || ts.isNoSubstitutionTemplateLiteral(init.expression)))
|
|
63
|
+
value = init.expression.text;
|
|
64
|
+
}
|
|
65
|
+
if (value === null)
|
|
66
|
+
unresolved = true;
|
|
67
|
+
else
|
|
68
|
+
values.push(value);
|
|
69
|
+
}
|
|
70
|
+
ts.forEachChild(node, visit);
|
|
71
|
+
};
|
|
72
|
+
visit(sf);
|
|
73
|
+
return unresolved ? null : values;
|
|
74
|
+
}
|
|
75
|
+
export function scanFile(file, code) {
|
|
76
|
+
const result = { namespaces: [], dynamicCalls: [], usesRoot: false };
|
|
77
|
+
if (!code.includes("useTranslations"))
|
|
78
|
+
return result;
|
|
79
|
+
const sf = ts.createSourceFile(file, code, ts.ScriptTarget.Latest, true, file.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
80
|
+
const hookNames = new Set();
|
|
81
|
+
const consts = new Map();
|
|
82
|
+
const typeAliases = new Map();
|
|
83
|
+
const paramTypes = new Map();
|
|
84
|
+
const propParams = new Map();
|
|
85
|
+
const collect = (node) => {
|
|
86
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && HOOK_MODULES.has(node.moduleSpecifier.text)) {
|
|
87
|
+
const named = node.importClause?.namedBindings;
|
|
88
|
+
if (named && ts.isNamedImports(named)) {
|
|
89
|
+
for (const el of named.elements)
|
|
90
|
+
if ((el.propertyName ?? el.name).text === "useTranslations")
|
|
91
|
+
hookNames.add(el.name.text);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer)
|
|
95
|
+
consts.set(node.name.text, node.initializer);
|
|
96
|
+
if (ts.isTypeAliasDeclaration(node))
|
|
97
|
+
typeAliases.set(node.name.text, node.type);
|
|
98
|
+
if (ts.isParameter(node) && node.type) {
|
|
99
|
+
if (ts.isIdentifier(node.name))
|
|
100
|
+
paramTypes.set(node.name.text, node.type);
|
|
101
|
+
const component = componentName(node.parent);
|
|
102
|
+
if (component && ts.isObjectBindingPattern(node.name)) {
|
|
103
|
+
for (const el of node.name.elements) {
|
|
104
|
+
if (!ts.isIdentifier(el.name))
|
|
105
|
+
continue;
|
|
106
|
+
const key = (el.propertyName && ts.isIdentifier(el.propertyName) ? el.propertyName : el.name).text;
|
|
107
|
+
propParams.set(el.name.text, { component, name: key });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (ts.isObjectBindingPattern(node.name) && ts.isTypeLiteralNode(node.type)) {
|
|
111
|
+
for (const el of node.name.elements) {
|
|
112
|
+
if (!ts.isIdentifier(el.name))
|
|
113
|
+
continue;
|
|
114
|
+
const key = (el.propertyName && ts.isIdentifier(el.propertyName) ? el.propertyName : el.name).text;
|
|
115
|
+
const member = node.type.members.find((m) => ts.isPropertySignature(m) && ts.isIdentifier(m.name) && m.name.text === key);
|
|
116
|
+
if (member?.type)
|
|
117
|
+
paramTypes.set(el.name.text, member.type);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
ts.forEachChild(node, collect);
|
|
122
|
+
};
|
|
123
|
+
collect(sf);
|
|
124
|
+
if (hookNames.size === 0)
|
|
125
|
+
return result;
|
|
126
|
+
const resolve = (expr, depth = 0) => {
|
|
127
|
+
if (depth > 5)
|
|
128
|
+
return null;
|
|
129
|
+
if (ts.isStringLiteral(expr) || ts.isNoSubstitutionTemplateLiteral(expr))
|
|
130
|
+
return [expr.text];
|
|
131
|
+
if (ts.isTemplateExpression(expr) && expr.head.text.includes(".")) {
|
|
132
|
+
const top = expr.head.text.split(".")[0];
|
|
133
|
+
if (top)
|
|
134
|
+
return [top];
|
|
135
|
+
}
|
|
136
|
+
if (ts.isParenthesizedExpression(expr) || ts.isAsExpression(expr))
|
|
137
|
+
return resolve(expr.expression, depth + 1);
|
|
138
|
+
if (ts.isConditionalExpression(expr)) {
|
|
139
|
+
const a = resolve(expr.whenTrue, depth + 1);
|
|
140
|
+
const b = resolve(expr.whenFalse, depth + 1);
|
|
141
|
+
return a && b ? [...a, ...b] : null;
|
|
142
|
+
}
|
|
143
|
+
if (ts.isIdentifier(expr)) {
|
|
144
|
+
const init = consts.get(expr.text);
|
|
145
|
+
if (init)
|
|
146
|
+
return resolve(init, depth + 1);
|
|
147
|
+
return literalsOfType(paramTypes.get(expr.text), sf, typeAliases);
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
};
|
|
151
|
+
const found = new Set();
|
|
152
|
+
const visit = (node) => {
|
|
153
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && hookNames.has(node.expression.text)) {
|
|
154
|
+
const arg = node.arguments[0];
|
|
155
|
+
if (!arg)
|
|
156
|
+
result.usesRoot = true;
|
|
157
|
+
else {
|
|
158
|
+
const values = resolve(arg);
|
|
159
|
+
if (values)
|
|
160
|
+
values.forEach((v) => found.add(topLevel(v)));
|
|
161
|
+
else {
|
|
162
|
+
const call = { file, line: sf.getLineAndCharacterOfPosition(arg.getStart()).line + 1, text: arg.getText() };
|
|
163
|
+
const prop = propOf(arg, propParams);
|
|
164
|
+
if (prop)
|
|
165
|
+
call.prop = prop;
|
|
166
|
+
result.dynamicCalls.push(call);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
ts.forEachChild(node, visit);
|
|
171
|
+
};
|
|
172
|
+
visit(sf);
|
|
173
|
+
result.namespaces = [...found];
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { matchesNamespacePattern } from "./match_namespace.js";
|
|
2
|
+
import { type DynamicCall } from "./scan_file.js";
|
|
3
|
+
export { matchesNamespacePattern };
|
|
4
|
+
export interface ScanProjectOptions {
|
|
5
|
+
root: string;
|
|
6
|
+
dirs?: string[];
|
|
7
|
+
packages?: string[];
|
|
8
|
+
fallbackNamespaces?: string[];
|
|
9
|
+
onDynamic?: "all" | "fallback";
|
|
10
|
+
}
|
|
11
|
+
export interface ClientMessagesManifest {
|
|
12
|
+
namespaces: string[] | true;
|
|
13
|
+
dynamicCalls: DynamicCall[];
|
|
14
|
+
scannedFiles: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function scanProject(opts: ScanProjectOptions): ClientMessagesManifest;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { matchesNamespacePattern } from "./match_namespace.js";
|
|
4
|
+
import { findJsxPropValues, scanFile } from "./scan_file.js";
|
|
5
|
+
export { matchesNamespacePattern };
|
|
6
|
+
const EXT = /\.(tsx?|jsx?|mjs)$/;
|
|
7
|
+
const SKIP_DIRS = new Set(["node_modules", "dist", ".next", ".git"]);
|
|
8
|
+
function walk(dir, out, skipNodeModules) {
|
|
9
|
+
for (const name of readdirSync(dir)) {
|
|
10
|
+
const full = join(dir, name);
|
|
11
|
+
if (statSync(full).isDirectory()) {
|
|
12
|
+
if (SKIP_DIRS.has(name) && (skipNodeModules || name !== "dist"))
|
|
13
|
+
continue;
|
|
14
|
+
walk(full, out, skipNodeModules);
|
|
15
|
+
}
|
|
16
|
+
else if (EXT.test(name) && !/\.(test|spec|bench)\./.test(name) && !name.endsWith(".d.ts"))
|
|
17
|
+
out.push(full);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function scanProject(opts) {
|
|
21
|
+
const files = [];
|
|
22
|
+
for (const d of opts.dirs ?? ["src", "app", "components"]) {
|
|
23
|
+
const full = join(opts.root, d);
|
|
24
|
+
if (existsSync(full))
|
|
25
|
+
walk(full, files, true);
|
|
26
|
+
}
|
|
27
|
+
const found = new Set(opts.fallbackNamespaces ?? []);
|
|
28
|
+
for (const pkg of opts.packages ?? []) {
|
|
29
|
+
const pkgDir = join(opts.root, "node_modules", pkg);
|
|
30
|
+
const pjPath = join(pkgDir, "package.json");
|
|
31
|
+
if (!existsSync(pjPath))
|
|
32
|
+
continue;
|
|
33
|
+
const pj = JSON.parse(readFileSync(pjPath, "utf8"));
|
|
34
|
+
if (Array.isArray(pj.i18n?.namespaces))
|
|
35
|
+
pj.i18n.namespaces.forEach((n) => found.add(n));
|
|
36
|
+
else
|
|
37
|
+
walk(pkgDir, files, false);
|
|
38
|
+
}
|
|
39
|
+
let dynamicCalls = [];
|
|
40
|
+
let usesRoot = false;
|
|
41
|
+
const sources = new Map();
|
|
42
|
+
for (const f of files) {
|
|
43
|
+
const code = readFileSync(f, "utf8");
|
|
44
|
+
sources.set(f, code);
|
|
45
|
+
const r = scanFile(f, code);
|
|
46
|
+
r.namespaces.forEach((n) => found.add(n));
|
|
47
|
+
dynamicCalls.push(...r.dynamicCalls);
|
|
48
|
+
usesRoot || (usesRoot = r.usesRoot);
|
|
49
|
+
}
|
|
50
|
+
dynamicCalls = dynamicCalls.filter((call) => {
|
|
51
|
+
if (!call.prop)
|
|
52
|
+
return true;
|
|
53
|
+
const values = [];
|
|
54
|
+
for (const [f, code] of sources) {
|
|
55
|
+
const v = findJsxPropValues(f, code, call.prop.component, call.prop.name);
|
|
56
|
+
if (v === null)
|
|
57
|
+
return true;
|
|
58
|
+
values.push(...v);
|
|
59
|
+
}
|
|
60
|
+
if (values.length === 0)
|
|
61
|
+
return true;
|
|
62
|
+
values.forEach((v) => found.add(v.split(".")[0]));
|
|
63
|
+
return false;
|
|
64
|
+
});
|
|
65
|
+
const sendAll = usesRoot || (dynamicCalls.length > 0 && (opts.onDynamic ?? "all") === "all");
|
|
66
|
+
return { namespaces: sendAll ? true : [...found].sort(), dynamicCalls, scannedFiles: files.length };
|
|
67
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { usePathname } from 'next/navigation.js';
|
|
3
|
-
import { useReportWebVitals } from 'next/web-vitals';
|
|
3
|
+
import { useReportWebVitals } from 'next/web-vitals.js';
|
|
4
4
|
import { useEffect } from 'react';
|
|
5
5
|
import useCookieConsent from '../use_cookie_consent.js';
|
|
6
6
|
const metricEventNames = {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, Suspense } from 'react';
|
|
3
|
+
import { useEffect, useSyncExternalStore, Suspense } from 'react';
|
|
4
4
|
import dynamic from 'next/dynamic.js';
|
|
5
5
|
import useCookieConsent from '../use_cookie_consent.js';
|
|
6
6
|
const ClarityScript = dynamic(() => import('./clarity_script.js'));
|
|
7
|
+
const noopSubscribe = () => () => undefined;
|
|
7
8
|
export default function CookieConsentAnalytics({ config }) {
|
|
8
9
|
const { consent, requiresConsent } = useCookieConsent();
|
|
9
10
|
const granted = consent === true || !requiresConsent;
|
|
11
|
+
const hydrated = useSyncExternalStore(noopSubscribe, () => true, () => false);
|
|
10
12
|
useEffect(() => {
|
|
11
13
|
if (consent === null && requiresConsent)
|
|
12
14
|
return;
|
|
@@ -21,8 +23,19 @@ export default function CookieConsentAnalytics({ config }) {
|
|
|
21
23
|
analytics_storage: state,
|
|
22
24
|
});
|
|
23
25
|
}, [consent, requiresConsent, granted]);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!granted || !config.cloudflareBeaconToken)
|
|
28
|
+
return;
|
|
29
|
+
if (document.querySelector('script[data-cf-beacon]'))
|
|
30
|
+
return;
|
|
31
|
+
const script = document.createElement('script');
|
|
32
|
+
script.defer = true;
|
|
33
|
+
script.src = 'https://static.cloudflareinsights.com/beacon.min.js';
|
|
34
|
+
script.setAttribute('data-cf-beacon', config.cloudflareBeaconToken);
|
|
35
|
+
document.head.appendChild(script);
|
|
36
|
+
}, [granted, config.cloudflareBeaconToken]);
|
|
24
37
|
const hasGoogle = Boolean(config.googleAnalyticsId || config.googleAdsId || config.googleAdSenseId);
|
|
25
|
-
return (_jsxs(_Fragment, { children: [hasGoogle && (_jsx("script", { id: "cookie-consent-google-consent-mode", dangerouslySetInnerHTML: { __html: googleConsentModeBootstrapScript(config) } })),
|
|
38
|
+
return (_jsxs(_Fragment, { children: [hasGoogle && (_jsx("script", { id: "cookie-consent-google-consent-mode", dangerouslySetInnerHTML: { __html: googleConsentModeBootstrapScript(config) } })), hydrated && granted && config.clarityProjectId && (_jsx(Suspense, { fallback: null, children: _jsx(ClarityScript, { projectId: config.clarityProjectId }) }))] }));
|
|
26
39
|
}
|
|
27
40
|
export function googleConsentModeBootstrapScript(config) {
|
|
28
41
|
const configCalls = [config.googleAnalyticsId, config.googleAdsId]
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useSyncExternalStore } from 'react';
|
|
3
4
|
import useCookieConsent from '../use_cookie_consent.js';
|
|
4
5
|
import DefaultPrivacyPolicyLink from './default_privacy_policy_link.js';
|
|
5
6
|
import DialogPortal from './dialog_portal.js';
|
|
6
7
|
import { getLocaleCache } from '../../../general/cache_variables.js';
|
|
7
8
|
import { defaultCookieConsentText } from './default_dialog_text.js';
|
|
8
9
|
import { defaultCookieDialogClassNames } from './default_dialog_styles.js';
|
|
10
|
+
const noopSubscribe = () => () => undefined;
|
|
9
11
|
export default function CookieConsentDialog({ message, link, privacyPolicyLinkText, showPrivacyPolicy, acceptText, declineText, hideDecline = false, id = 'cookie-consent-dialog', classNames, styles, render, }) {
|
|
10
12
|
const { consent, requiresConsent, isMounted, setConsent, privacyPolicyPath, showPrivacyPolicy: showPrivacyPolicyCtx } = useCookieConsent();
|
|
11
|
-
|
|
13
|
+
const hydrated = useSyncExternalStore(noopSubscribe, () => true, () => false);
|
|
14
|
+
if (!hydrated || !isMounted || !requiresConsent || consent !== null)
|
|
12
15
|
return null;
|
|
13
16
|
if (render)
|
|
14
17
|
return _jsx(_Fragment, { children: render({ setConsent }) });
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useSyncExternalStore } from 'react';
|
|
3
4
|
import useCookieConsent from '../use_cookie_consent.js';
|
|
4
5
|
import DefaultPrivacyPolicyLink from './default_privacy_policy_link.js';
|
|
5
6
|
import DialogPortal from './dialog_portal.js';
|
|
6
7
|
import { getLocaleCache } from '../../../general/cache_variables.js';
|
|
7
8
|
import { defaultPrivacyPolicyUpdateText } from './default_dialog_text.js';
|
|
8
9
|
import { defaultCookieDialogClassNames } from './default_dialog_styles.js';
|
|
10
|
+
const noopSubscribe = () => () => undefined;
|
|
9
11
|
export default function PrivacyPolicyUpdateDialog({ message, link, privacyPolicyLinkText, showPrivacyPolicy, closeText, id = 'privacy-policy-update-dialog', classNames, styles, render, }) {
|
|
10
12
|
const { privacyPolicyUpdated, acknowledgePrivacyPolicyUpdate, privacyPolicyPath, showPrivacyPolicy: showPrivacyPolicyCtx } = useCookieConsent();
|
|
11
|
-
|
|
13
|
+
const hydrated = useSyncExternalStore(noopSubscribe, () => true, () => false);
|
|
14
|
+
if (!hydrated || !privacyPolicyUpdated)
|
|
12
15
|
return null;
|
|
13
16
|
if (render)
|
|
14
17
|
return _jsx(_Fragment, { children: render({ acknowledge: acknowledgePrivacyPolicyUpdate }) });
|
|
@@ -3,6 +3,7 @@ export { default as useCookieConsent } from './client/use_cookie_consent.js';
|
|
|
3
3
|
export { default as CookieConsentDialog } from './client/components/cookie_consent_dialog.js';
|
|
4
4
|
export { default as PrivacyPolicyUpdateDialog } from './client/components/privacy_policy_update_dialog.js';
|
|
5
5
|
export { default as CookieConsentAnalytics } from './client/components/cookie_consent_analytics.js';
|
|
6
|
+
export { default as AutoAnalyticsEvents } from './client/components/auto_analytics_events.js';
|
|
6
7
|
export { defaultGdprCountries } from './gdpr_countries.js';
|
|
7
8
|
export type { CookieConsentContextType, ConsentValue, CookieDialogClassNames, CookieDialogStyles } from './types.js';
|
|
8
9
|
export type { CookieConsentDialogProps } from './client/components/cookie_consent_dialog.js';
|
|
@@ -3,4 +3,5 @@ export { default as useCookieConsent } from './client/use_cookie_consent.js';
|
|
|
3
3
|
export { default as CookieConsentDialog } from './client/components/cookie_consent_dialog.js';
|
|
4
4
|
export { default as PrivacyPolicyUpdateDialog } from './client/components/privacy_policy_update_dialog.js';
|
|
5
5
|
export { default as CookieConsentAnalytics } from './client/components/cookie_consent_analytics.js';
|
|
6
|
+
export { default as AutoAnalyticsEvents } from './client/components/auto_analytics_events.js';
|
|
6
7
|
export { defaultGdprCountries } from './gdpr_countries.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { matchesNamespacePattern } from "../client_messages_scan/match_namespace.js";
|
|
2
|
+
function readAutoManifest() {
|
|
3
|
+
return typeof __CFNI_CLIENT_MESSAGES__ === "undefined" ? undefined : __CFNI_CLIENT_MESSAGES__;
|
|
4
|
+
}
|
|
5
|
+
export default function pickClientMessages(messages, clientMessages, autoManifest = readAutoManifest()) {
|
|
6
|
+
const list = clientMessages === "auto" ? autoManifest ?? true : clientMessages;
|
|
7
|
+
if (list === undefined || list === true)
|
|
8
|
+
return messages;
|
|
9
|
+
const picked = {};
|
|
10
|
+
if (list === false)
|
|
11
|
+
return picked;
|
|
12
|
+
for (const namespace of Object.keys(messages)) {
|
|
13
|
+
if (list.some((p) => matchesNamespacePattern(namespace, p)))
|
|
14
|
+
picked[namespace] = messages[namespace];
|
|
15
|
+
}
|
|
16
|
+
return picked;
|
|
17
|
+
}
|
|
@@ -22,6 +22,14 @@ catch {
|
|
|
22
22
|
const images = (manifestData && typeof manifestData === "object" && manifestData.images)
|
|
23
23
|
? manifestData.images
|
|
24
24
|
: {};
|
|
25
|
+
let defaultCustomLoader;
|
|
26
|
+
try {
|
|
27
|
+
const loaderMod = (await import("virtual:cloudflare-next-intl-image-loader"));
|
|
28
|
+
defaultCustomLoader = loaderMod.defaultLoader ?? loaderMod.default;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
defaultCustomLoader = undefined;
|
|
32
|
+
}
|
|
25
33
|
function findEntry(srcVal) {
|
|
26
34
|
if (!srcVal)
|
|
27
35
|
return undefined;
|
|
@@ -85,6 +93,7 @@ function resolveProps(props) {
|
|
|
85
93
|
let width = props.width;
|
|
86
94
|
let height = props.height;
|
|
87
95
|
let style = props.style;
|
|
96
|
+
const loader = props.loader ?? defaultCustomLoader;
|
|
88
97
|
const entry = findEntry(src);
|
|
89
98
|
if (entry) {
|
|
90
99
|
const variant = pickVariant(entry, typeof props.width === "number" ? props.width : undefined);
|
|
@@ -117,7 +126,7 @@ function resolveProps(props) {
|
|
|
117
126
|
...style,
|
|
118
127
|
};
|
|
119
128
|
}
|
|
120
|
-
return { ...props, src, blurDataURL, width, height, style };
|
|
129
|
+
return { ...props, src, blurDataURL, width, height, style, loader };
|
|
121
130
|
}
|
|
122
131
|
export default function Image(props) {
|
|
123
132
|
const resolved = resolveProps(props);
|
|
@@ -5,6 +5,7 @@ import { Suspense } from "react";
|
|
|
5
5
|
import dynamic from "next/dynamic.js";
|
|
6
6
|
import { localesSet } from "../../config/middleware.js";
|
|
7
7
|
import config from "../../config/intl_config.js";
|
|
8
|
+
import pickClientMessages from "../../general/pick_client_messages.js";
|
|
8
9
|
import installConsoleErrorOverride from "../../error_handling/install_console_error_override.js";
|
|
9
10
|
import reportError from "../../error_handling/report_error.js";
|
|
10
11
|
const LocationzationClientProvider = dynamic(() => import("../../client/components/client_provider.js"));
|
|
@@ -21,7 +22,7 @@ export default async function LocationzationProvider({ language, messages, stati
|
|
|
21
22
|
if (messages) {
|
|
22
23
|
setMessageForLocaleCache(language, messages);
|
|
23
24
|
}
|
|
24
|
-
const messagesValue = messages ?? await getMessage(language);
|
|
25
|
+
const messagesValue = pickClientMessages(messages ?? await getMessage(language), config.clientMessages);
|
|
25
26
|
installConsoleErrorOverride(config);
|
|
26
27
|
let initialAuthUser = null;
|
|
27
28
|
const autoWireClientProvider = config.firebaseAuth?.autoWireClientProvider !== false;
|
|
@@ -5,6 +5,7 @@ import { Suspense } from "react";
|
|
|
5
5
|
import dynamic from "next/dynamic.js";
|
|
6
6
|
import { localesSet } from "../../config/middleware.js";
|
|
7
7
|
import config from "../../config/intl_config.js";
|
|
8
|
+
import pickClientMessages from "../../general/pick_client_messages.js";
|
|
8
9
|
import installConsoleErrorOverride from "../../error_handling/install_console_error_override.js";
|
|
9
10
|
import reportError from "../../error_handling/report_error.js";
|
|
10
11
|
const LocationzationClientProvider = dynamic(() => import("../../client/components/client_provider_static.js"));
|
|
@@ -26,7 +27,7 @@ export default async function LocationzationProvider({ language, messages, child
|
|
|
26
27
|
if (messages) {
|
|
27
28
|
setMessageForLocaleCache(language, messages);
|
|
28
29
|
}
|
|
29
|
-
const messagesValue = messages ?? await getMessage(language);
|
|
30
|
+
const messagesValue = pickClientMessages(messages ?? await getMessage(language), config.clientMessages);
|
|
30
31
|
installConsoleErrorOverride(config);
|
|
31
32
|
let analyticsConfig;
|
|
32
33
|
let requiresConsent = true;
|
|
@@ -19,6 +19,7 @@ export interface RoutingConfig<AppLocales extends Locales, AppLocalePrefixMode e
|
|
|
19
19
|
db?: DbRoutingConfig;
|
|
20
20
|
cookieConsent?: CookieConsentRoutingConfig;
|
|
21
21
|
generate?: GenerateRoutingConfig;
|
|
22
|
+
clientMessages?: boolean | "auto" | readonly string[];
|
|
22
23
|
errorHandling?: ErrorHandlingRoutingConfig;
|
|
23
24
|
link?: LinkRoutingConfig;
|
|
24
25
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import { type ScanProjectOptions } from "../client_messages_scan/scan_project.js";
|
|
3
|
+
export interface AutoClientMessagesOptions extends Partial<Omit<ScanProjectOptions, "root">> {
|
|
4
|
+
root?: string;
|
|
5
|
+
strict?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function autoClientMessagesPlugin(options?: AutoClientMessagesOptions): Plugin;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { scanProject } from "../client_messages_scan/scan_project.js";
|
|
2
|
+
const KEY = "__CFNI_CLIENT_MESSAGES__";
|
|
3
|
+
const SOURCE = /\.(tsx?|jsx?|mjs)$/;
|
|
4
|
+
export function autoClientMessagesPlugin(options = {}) {
|
|
5
|
+
let last = "";
|
|
6
|
+
const run = () => {
|
|
7
|
+
const manifest = scanProject({ ...options, root: options.root ?? process.cwd() });
|
|
8
|
+
for (const d of manifest.dynamicCalls)
|
|
9
|
+
console.warn(`[cloudflare-next-intl] dynamic useTranslations: ${d.file}:${d.line} ${d.text}`);
|
|
10
|
+
if (options.strict && manifest.dynamicCalls.length > 0)
|
|
11
|
+
throw new Error("[cloudflare-next-intl] Dynamic useTranslations namespaces found. Use literals, union types, or fallbackNamespaces with onDynamic: \"fallback\".");
|
|
12
|
+
console.log(manifest.namespaces === true
|
|
13
|
+
? `[cloudflare-next-intl] client messages: sending all (root or dynamic access) from ${manifest.scannedFiles} files`
|
|
14
|
+
: `[cloudflare-next-intl] client messages: ${manifest.namespaces.length} namespaces from ${manifest.scannedFiles} files`);
|
|
15
|
+
return JSON.stringify(manifest.namespaces);
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
name: "cloudflare-next-intl-auto-client-messages",
|
|
19
|
+
config() {
|
|
20
|
+
last = run();
|
|
21
|
+
return { define: { [KEY]: last } };
|
|
22
|
+
},
|
|
23
|
+
handleHotUpdate({ file, server }) {
|
|
24
|
+
if (!SOURCE.test(file))
|
|
25
|
+
return;
|
|
26
|
+
const next = run();
|
|
27
|
+
if (next === last)
|
|
28
|
+
return;
|
|
29
|
+
last = next;
|
|
30
|
+
server.config.define[KEY] = next;
|
|
31
|
+
server.ws.send({ type: "full-reload" });
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
export declare const VIRTUAL_IMAGE_LOADER_ID = "virtual:cloudflare-next-intl-image-loader";
|
|
3
|
+
export declare const RESOLVED_IMAGE_LOADER_ID: string;
|
|
4
|
+
export declare const DEFAULT_LOADER_CANDIDATES: readonly ["image-loader.ts", "image-loader.js", "image-loader.mjs", "image-loader.cjs", "image-loader.tsx", "image-loader.jsx", "src/image-loader.ts", "src/image-loader.js", "src/image-loader.mjs", "src/image-loader.cjs", "src/image-loader.tsx", "src/image-loader.jsx"];
|
|
5
|
+
export interface AutoImageLoaderOptions {
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
file?: string;
|
|
8
|
+
exportName?: string;
|
|
9
|
+
root?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DetectedImageLoader {
|
|
12
|
+
exists: boolean;
|
|
13
|
+
isEmpty: boolean;
|
|
14
|
+
loaderPath: string | null;
|
|
15
|
+
exportName: string | null;
|
|
16
|
+
}
|
|
17
|
+
export declare function findExportedLoaderName(code: string, preferredExportName?: string): string | null;
|
|
18
|
+
export declare function detectImageLoader(options?: AutoImageLoaderOptions): DetectedImageLoader;
|
|
19
|
+
export declare function generateLoaderVirtualModule(detected: DetectedImageLoader): string;
|
|
20
|
+
export declare function autoImageLoaderPlugin(options?: AutoImageLoaderOptions): Plugin;
|
|
21
|
+
export default autoImageLoaderPlugin;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getShimPath, VIRTUAL_IMAGE_SHIM_ID } from "../image_optimizer/plugin.js";
|
|
4
|
+
export const VIRTUAL_IMAGE_LOADER_ID = "virtual:cloudflare-next-intl-image-loader";
|
|
5
|
+
export const RESOLVED_IMAGE_LOADER_ID = "\0" + VIRTUAL_IMAGE_LOADER_ID;
|
|
6
|
+
export const DEFAULT_LOADER_CANDIDATES = [
|
|
7
|
+
"image-loader.ts",
|
|
8
|
+
"image-loader.js",
|
|
9
|
+
"image-loader.mjs",
|
|
10
|
+
"image-loader.cjs",
|
|
11
|
+
"image-loader.tsx",
|
|
12
|
+
"image-loader.jsx",
|
|
13
|
+
"src/image-loader.ts",
|
|
14
|
+
"src/image-loader.js",
|
|
15
|
+
"src/image-loader.mjs",
|
|
16
|
+
"src/image-loader.cjs",
|
|
17
|
+
"src/image-loader.tsx",
|
|
18
|
+
"src/image-loader.jsx",
|
|
19
|
+
];
|
|
20
|
+
export function findExportedLoaderName(code, preferredExportName) {
|
|
21
|
+
const stripped = code
|
|
22
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
23
|
+
.replace(/\/\/.*/g, "");
|
|
24
|
+
if (preferredExportName) {
|
|
25
|
+
if (preferredExportName === "default") {
|
|
26
|
+
if (/export\s+default\b/.test(stripped) || /export\s*\{[^}]*\bas\s+default\b[^}]*\}/.test(stripped)) {
|
|
27
|
+
return "default";
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const fnRegex = new RegExp(`export\\s+(?:async\\s+)?function\\s+${preferredExportName}\\b`);
|
|
32
|
+
if (fnRegex.test(stripped))
|
|
33
|
+
return preferredExportName;
|
|
34
|
+
const constRegex = new RegExp(`export\\s+(?:const|let|var)\\s+${preferredExportName}\\b`);
|
|
35
|
+
if (constRegex.test(stripped))
|
|
36
|
+
return preferredExportName;
|
|
37
|
+
const reExportRegex = new RegExp(`export\\s*\\{[^}]*\\b(?:as\\s+)?${preferredExportName}\\b[^}]*\\}`);
|
|
38
|
+
if (reExportRegex.test(stripped))
|
|
39
|
+
return preferredExportName;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (/export\s+default\b/.test(stripped) || /export\s*\{[^}]*\bas\s+default\b[^}]*\}/.test(stripped)) {
|
|
43
|
+
return "default";
|
|
44
|
+
}
|
|
45
|
+
const fnMatch = stripped.match(/export\s+(?:async\s+)?function\s+([a-zA-Z0-9_$]+)/);
|
|
46
|
+
if (fnMatch?.[1]) {
|
|
47
|
+
return fnMatch[1];
|
|
48
|
+
}
|
|
49
|
+
const varMatch = stripped.match(/export\s+(?:const|let|var)\s+([a-zA-Z0-9_$]+)/);
|
|
50
|
+
if (varMatch?.[1]) {
|
|
51
|
+
return varMatch[1];
|
|
52
|
+
}
|
|
53
|
+
const exportBlockMatch = stripped.match(/export\s*\{([^}]+)\}/);
|
|
54
|
+
if (exportBlockMatch?.[1]) {
|
|
55
|
+
const entries = exportBlockMatch[1].split(",");
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
const parts = entry.trim().split(/\s+as\s+/);
|
|
58
|
+
const exportedName = parts[parts.length - 1]?.trim();
|
|
59
|
+
if (exportedName) {
|
|
60
|
+
return exportedName;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
export function detectImageLoader(options) {
|
|
67
|
+
const root = options?.root ? path.resolve(options.root) : process.cwd();
|
|
68
|
+
let targetPath = null;
|
|
69
|
+
if (options?.file) {
|
|
70
|
+
const customPath = path.isAbsolute(options.file) ? options.file : path.resolve(root, options.file);
|
|
71
|
+
if (existsSync(customPath)) {
|
|
72
|
+
targetPath = customPath;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
return { exists: false, isEmpty: false, loaderPath: null, exportName: null };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
for (const candidate of DEFAULT_LOADER_CANDIDATES) {
|
|
80
|
+
const candidatePath = path.resolve(root, candidate);
|
|
81
|
+
if (existsSync(candidatePath)) {
|
|
82
|
+
targetPath = candidatePath;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!targetPath) {
|
|
88
|
+
return { exists: false, isEmpty: false, loaderPath: null, exportName: null };
|
|
89
|
+
}
|
|
90
|
+
const content = readFileSync(targetPath, "utf8");
|
|
91
|
+
const stripped = content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*/g, "").trim();
|
|
92
|
+
if (!stripped) {
|
|
93
|
+
return { exists: true, isEmpty: true, loaderPath: targetPath, exportName: null };
|
|
94
|
+
}
|
|
95
|
+
const exportName = findExportedLoaderName(content, options?.exportName);
|
|
96
|
+
return {
|
|
97
|
+
exists: true,
|
|
98
|
+
isEmpty: false,
|
|
99
|
+
loaderPath: targetPath,
|
|
100
|
+
exportName,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function generateLoaderVirtualModule(detected) {
|
|
104
|
+
if (!detected.exists || detected.isEmpty || !detected.loaderPath || !detected.exportName) {
|
|
105
|
+
return `export const defaultLoader = undefined;\nexport const hasCustomLoader = false;\nexport default undefined;\n`;
|
|
106
|
+
}
|
|
107
|
+
const normalizedPath = detected.loaderPath.replace(/\\/g, "/");
|
|
108
|
+
if (detected.exportName === "default") {
|
|
109
|
+
return `import loader from ${JSON.stringify(normalizedPath)};\nexport const defaultLoader = loader;\nexport const hasCustomLoader = true;\nexport default loader;\n`;
|
|
110
|
+
}
|
|
111
|
+
return `import { ${detected.exportName} as loader } from ${JSON.stringify(normalizedPath)};\nexport const defaultLoader = loader;\nexport const hasCustomLoader = true;\nexport default loader;\n`;
|
|
112
|
+
}
|
|
113
|
+
export function autoImageLoaderPlugin(options) {
|
|
114
|
+
const isEnabled = options?.enabled !== false;
|
|
115
|
+
return {
|
|
116
|
+
name: "cloudflare-next-intl-auto-image-loader",
|
|
117
|
+
enforce: "pre",
|
|
118
|
+
resolveId(id) {
|
|
119
|
+
if (id === VIRTUAL_IMAGE_LOADER_ID) {
|
|
120
|
+
return RESOLVED_IMAGE_LOADER_ID;
|
|
121
|
+
}
|
|
122
|
+
if (id === VIRTUAL_IMAGE_SHIM_ID) {
|
|
123
|
+
return getShimPath();
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
},
|
|
127
|
+
load(id) {
|
|
128
|
+
if (id === RESOLVED_IMAGE_LOADER_ID) {
|
|
129
|
+
if (!isEnabled) {
|
|
130
|
+
return `export const defaultLoader = undefined;\nexport const hasCustomLoader = false;\nexport default undefined;\n`;
|
|
131
|
+
}
|
|
132
|
+
const detected = detectImageLoader(options);
|
|
133
|
+
return generateLoaderVirtualModule(detected);
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
},
|
|
137
|
+
transform(code, id) {
|
|
138
|
+
if (!isEnabled)
|
|
139
|
+
return undefined;
|
|
140
|
+
if (id.includes("node_modules"))
|
|
141
|
+
return undefined;
|
|
142
|
+
if (id === getShimPath())
|
|
143
|
+
return undefined;
|
|
144
|
+
if (!/from\s*["']next\/image["']/.test(code))
|
|
145
|
+
return undefined;
|
|
146
|
+
const next = code.replace(/(import\s+(?!type\s)[^;]*?from\s*)(["'])next\/image\2/g, `$1$2${VIRTUAL_IMAGE_SHIM_ID}$2`);
|
|
147
|
+
return next === code ? undefined : { code: next, map: null };
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export default autoImageLoaderPlugin;
|
package/dist/src/vite/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { autoClientMessagesPlugin, type AutoClientMessagesOptions } from "./auto_client_messages_plugin.js";
|
|
1
2
|
export { autoDynamicPagesPlugin, type AutoDynamicPagesPluginOptions } from "./auto_dynamic_pages_plugin.js";
|
|
2
3
|
export { autoLocaleParamsPlugin, type AutoLocaleParamsPluginOptions } from "./auto_locale_params_plugin.js";
|
|
3
4
|
export { layoutQueriesPlugin, type LayoutQueriesPluginOptions } from "./layout_queries_plugin.js";
|
|
@@ -11,5 +12,6 @@ export { reactEvalStubPlugin, reactEvalEsbuildPlugin, transformReactEval, EVAL_W
|
|
|
11
12
|
export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed, isVinextAppPageRouteWiringSafeOnDisk, patchAppPageProbe, isAppPageProbeFile, isAppPageProbeAlreadyFixed, isVinextAppPageProbeSafeOnDisk, type VinextRouteWiringFixPluginOptions, } from "./vinext_route_wiring_fix.js";
|
|
12
13
|
export { localeFilePlugin, resolveDefaultIntlConfigPath, type LocaleFilePluginOptions } from "./locale_file_plugin.js";
|
|
13
14
|
export { lucideOptimizerPlugin, detectLucideReact, resolveLucideEsmEntry, parseLucideIconMap, transformLucideImports, transformNextJsImports, type LucideOptimizerPluginOptions, } from "./lucide_optimizer_plugin.js";
|
|
15
|
+
export { autoImageLoaderPlugin, detectImageLoader, findExportedLoaderName, generateLoaderVirtualModule, DEFAULT_LOADER_CANDIDATES, VIRTUAL_IMAGE_LOADER_ID, RESOLVED_IMAGE_LOADER_ID, type AutoImageLoaderOptions, type DetectedImageLoader, } from "./auto_image_loader_plugin.js";
|
|
14
16
|
export { cloudflareNextIntl, cloudflareNextIntlPlugin, type CloudflareNextIntlOptions, default } from "./plugin.js";
|
|
15
17
|
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";
|
package/dist/src/vite/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { autoClientMessagesPlugin } from "./auto_client_messages_plugin.js";
|
|
1
2
|
export { autoDynamicPagesPlugin } from "./auto_dynamic_pages_plugin.js";
|
|
2
3
|
export { autoLocaleParamsPlugin } from "./auto_locale_params_plugin.js";
|
|
3
4
|
export { layoutQueriesPlugin } from "./layout_queries_plugin.js";
|
|
@@ -11,5 +12,6 @@ export { reactEvalStubPlugin, reactEvalEsbuildPlugin, transformReactEval, EVAL_W
|
|
|
11
12
|
export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed, isVinextAppPageRouteWiringSafeOnDisk, patchAppPageProbe, isAppPageProbeFile, isAppPageProbeAlreadyFixed, isVinextAppPageProbeSafeOnDisk, } from "./vinext_route_wiring_fix.js";
|
|
12
13
|
export { localeFilePlugin, resolveDefaultIntlConfigPath } from "./locale_file_plugin.js";
|
|
13
14
|
export { lucideOptimizerPlugin, detectLucideReact, resolveLucideEsmEntry, parseLucideIconMap, transformLucideImports, transformNextJsImports, } from "./lucide_optimizer_plugin.js";
|
|
15
|
+
export { autoImageLoaderPlugin, detectImageLoader, findExportedLoaderName, generateLoaderVirtualModule, DEFAULT_LOADER_CANDIDATES, VIRTUAL_IMAGE_LOADER_ID, RESOLVED_IMAGE_LOADER_ID, } from "./auto_image_loader_plugin.js";
|
|
14
16
|
export { cloudflareNextIntl, cloudflareNextIntlPlugin, default } from "./plugin.js";
|
|
15
17
|
export { imageOptimizer, imageOptimizerPlugin, VIRTUAL_IMAGE_SHIM_ID, } from "../image_optimizer/index.js";
|
|
@@ -3,6 +3,8 @@ 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
5
|
import { type AutoLocaleParamsPluginOptions } from "./auto_locale_params_plugin.js";
|
|
6
|
+
import { type AutoImageLoaderOptions } from "./auto_image_loader_plugin.js";
|
|
7
|
+
import { type AutoClientMessagesOptions } from "./auto_client_messages_plugin.js";
|
|
6
8
|
import { type LayoutQueriesPluginOptions } from "./layout_queries_plugin.js";
|
|
7
9
|
import { type FirebaseAuthCheckPluginOptions } from "./firebase_auth_check_plugin.js";
|
|
8
10
|
import { type VinextRouteWiringFixPluginOptions } from "./vinext_route_wiring_fix.js";
|
|
@@ -17,6 +19,8 @@ export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
|
|
|
17
19
|
bufferStub?: boolean;
|
|
18
20
|
reactEvalStub?: boolean;
|
|
19
21
|
optionalFirebaseStub?: boolean;
|
|
22
|
+
autoImageLoader?: boolean | AutoImageLoaderOptions;
|
|
23
|
+
autoClientMessages?: boolean | AutoClientMessagesOptions;
|
|
20
24
|
imageOptimizer?: boolean | ImageOptimizerPluginOptions;
|
|
21
25
|
vinextRouteWiringFix?: boolean | VinextRouteWiringFixPluginOptions;
|
|
22
26
|
autoLocaleParams?: boolean | AutoLocaleParamsPluginOptions;
|
package/dist/src/vite/plugin.js
CHANGED
|
@@ -8,6 +8,8 @@ import { localeFilePlugin } from "./locale_file_plugin.js";
|
|
|
8
8
|
import { imageOptimizerPlugin } from "../image_optimizer/index.js";
|
|
9
9
|
import { autoDynamicPagesPlugin } from "./auto_dynamic_pages_plugin.js";
|
|
10
10
|
import { autoLocaleParamsPlugin } from "./auto_locale_params_plugin.js";
|
|
11
|
+
import { autoImageLoaderPlugin } from "./auto_image_loader_plugin.js";
|
|
12
|
+
import { autoClientMessagesPlugin } from "./auto_client_messages_plugin.js";
|
|
11
13
|
import { layoutQueriesPlugin } from "./layout_queries_plugin.js";
|
|
12
14
|
import { firebaseAuthCheckPlugin } from "./firebase_auth_check_plugin.js";
|
|
13
15
|
import { vinextRouteWiringFixPlugin } from "./vinext_route_wiring_fix.js";
|
|
@@ -48,6 +50,16 @@ export function cloudflareNextIntl(options = {}) {
|
|
|
48
50
|
}
|
|
49
51
|
plugins.push(autoDynamicPagesPlugin(autoDynamicPagesOptions));
|
|
50
52
|
}
|
|
53
|
+
if (options.autoImageLoader !== false) {
|
|
54
|
+
plugins.push(autoImageLoaderPlugin(typeof options.autoImageLoader === "object"
|
|
55
|
+
? { root: options.root, ...options.autoImageLoader }
|
|
56
|
+
: { root: options.root }));
|
|
57
|
+
}
|
|
58
|
+
if (options.autoClientMessages) {
|
|
59
|
+
plugins.push(autoClientMessagesPlugin(typeof options.autoClientMessages === "object"
|
|
60
|
+
? { root: options.root, ...options.autoClientMessages }
|
|
61
|
+
: { root: options.root }));
|
|
62
|
+
}
|
|
51
63
|
if (options.imageOptimizer !== false) {
|
|
52
64
|
plugins.push(imageOptimizerPlugin(typeof options.imageOptimizer === "object"
|
|
53
65
|
? options.imageOptimizer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudflare-next-intl",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.28",
|
|
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",
|
|
@@ -180,6 +180,10 @@
|
|
|
180
180
|
"types": "./dist/src/cookie_consent/client/components/cookie_consent_analytics.d.ts",
|
|
181
181
|
"import": "./dist/src/cookie_consent/client/components/cookie_consent_analytics.js"
|
|
182
182
|
},
|
|
183
|
+
"./AutoAnalyticsEvents": {
|
|
184
|
+
"types": "./dist/src/cookie_consent/client/components/auto_analytics_events.d.ts",
|
|
185
|
+
"import": "./dist/src/cookie_consent/client/components/auto_analytics_events.js"
|
|
186
|
+
},
|
|
183
187
|
"./errorHandling": {
|
|
184
188
|
"types": "./dist/src/error_handling/index.d.ts",
|
|
185
189
|
"import": "./dist/src/error_handling/index.js"
|