cloudflare-next-intl 0.8.52 → 0.8.53
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 +37 -6
- package/dist/package.json +314 -0
- package/dist/src/config/middleware.js +21 -6
- package/dist/src/cookie_consent/client/components/cookie_consent_dialog.d.ts +6 -1
- package/dist/src/cookie_consent/client/components/cookie_consent_dialog.js +6 -3
- package/dist/src/cookie_consent/client/components/privacy_policy_update_dialog.d.ts +6 -1
- package/dist/src/cookie_consent/client/components/privacy_policy_update_dialog.js +6 -3
- package/dist/src/cookie_consent/client/cookie_consent_provider.js +3 -2
- package/dist/src/cookie_consent/gdpr_countries.d.ts +2 -2
- package/dist/src/cookie_consent/gdpr_countries.js +2 -2
- package/dist/src/cookie_consent/types.d.ts +5 -0
- package/dist/src/server/components/server_provider.js +1 -1
- package/dist/src/server/components/server_provider_static.js +1 -1
- package/dist/src/server/functions/geo.d.ts +2 -2
- package/dist/src/server/functions/geo.js +36 -12
- package/dist/src/types/types.d.ts +6 -0
- package/dist/src/vite/cf_workers_client_stub.d.ts +4 -0
- package/dist/src/vite/cf_workers_client_stub.js +23 -0
- package/dist/src/vite/index.d.ts +5 -0
- package/dist/src/vite/index.js +5 -0
- package/dist/src/vite/locale_file_plugin.d.ts +19 -0
- package/dist/src/vite/locale_file_plugin.js +90 -0
- package/dist/src/vite/plugin.d.ts +29 -0
- package/dist/src/vite/plugin.js +27 -0
- package/dist/src/vite/user_agent_stub.d.ts +4 -0
- package/dist/src/vite/user_agent_stub.js +49 -0
- package/llms.txt +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -239,22 +239,52 @@ export default setIntlConfig({
|
|
|
239
239
|
});
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
-
#### Vite
|
|
242
|
+
#### Vite Plugin for Vinext & Cloudflare Workers (`cloudflare-next-intl/vite`)
|
|
243
243
|
|
|
244
|
-
When
|
|
244
|
+
When using **Vinext** (Vite + Next.js App Router for Cloudflare Workers), add `cloudflareNextIntl()` to `vite.config.ts`. It is **required** for Vinext projects to resolve translations, bundle locale files, stub Node.js dependencies, and emit the build asset:
|
|
245
245
|
|
|
246
246
|
```typescript
|
|
247
247
|
// vite.config.ts
|
|
248
248
|
import { defineConfig } from "vite";
|
|
249
|
-
import {
|
|
249
|
+
import { cloudflareNextIntl } from "cloudflare-next-intl/vite";
|
|
250
250
|
|
|
251
251
|
export default defineConfig({
|
|
252
252
|
plugins: [
|
|
253
|
-
|
|
253
|
+
cloudflareNextIntl(), // All plugins enabled by default
|
|
254
254
|
],
|
|
255
255
|
});
|
|
256
256
|
```
|
|
257
257
|
|
|
258
|
+
##### What `cloudflareNextIntl()` Does
|
|
259
|
+
1. **Locale File Bundling & Resolution (`localeFiles`)**: Resolves `@locale-file/*` to your `./messages` directory and transforms dynamic imports into `import.meta.glob('/messages/*.json', { eager: true })` for lightning-fast locale loading on Cloudflare Workers.
|
|
260
|
+
2. **User-Agent Stub (`userAgentStub`)**: Prevents Next.js `user-agent` from importing `node:fs` during workerd runtime execution (which otherwise causes runtime 404 / 500 crashes in Workers proxy/middleware).
|
|
261
|
+
3. **Cloudflare Workers Client Stub (`cfWorkersClientStub`)**: Stubs `cloudflare:workers` in client builds so shared modules can be referenced without client bundling errors.
|
|
262
|
+
4. **Build ID Asset Emission (`buildIdAsset`)**: Emits `BUILD_ID` static asset in the client build directory from `process.env.__VINEXT_SHARED_BUILD_ID` or `process.env.__VINEXT_BUILD_ID`.
|
|
263
|
+
|
|
264
|
+
##### Plugin Options
|
|
265
|
+
All features are enabled by default, and can be individually configured or toggled off:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { defineConfig } from "vite";
|
|
269
|
+
import { cloudflareNextIntl } from "cloudflare-next-intl/vite";
|
|
270
|
+
|
|
271
|
+
export default defineConfig({
|
|
272
|
+
plugins: [
|
|
273
|
+
cloudflareNextIntl({
|
|
274
|
+
messagesDir: "./messages", // Path to locale JSON files (default: './messages')
|
|
275
|
+
intlConfigPath: "./src/l18n/intl_config.ts", // Path to intl config (auto-detected if omitted)
|
|
276
|
+
buildIdAsset: true, // Emit BUILD_ID asset (or custom string filename, default: true)
|
|
277
|
+
localeFiles: true, // Enable @locale-file & glob bundling (default: true)
|
|
278
|
+
userAgentStub: true, // Enable regex-based user-agent stub (default: true)
|
|
279
|
+
cfWorkersClientStub: true, // Enable client cloudflare:workers stub (default: true)
|
|
280
|
+
}),
|
|
281
|
+
],
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Individual standalone plugins are also exported if you only need a specific feature:
|
|
286
|
+
`buildIdAsset`, `localeFilePlugin`, `userAgentStubPlugin`, `cfWorkersClientStubPlugin`.
|
|
287
|
+
|
|
258
288
|
```tsx
|
|
259
289
|
// Client Components ("use client")
|
|
260
290
|
import { useLocale } from "cloudflare-next-intl/use";
|
|
@@ -405,6 +435,7 @@ export default setIntlConfig({
|
|
|
405
435
|
},
|
|
406
436
|
cookieConsent: {
|
|
407
437
|
privacyPolicyDate: "2026-01-01",
|
|
438
|
+
// showPrivacyPolicy: false, // defaults to true; set false to hide privacy policy link in dialogs
|
|
408
439
|
// privacyPolicyPath: "/privacy-policy", // default; used by the
|
|
409
440
|
// dialogs' auto-rendered link. Set false to disable that link.
|
|
410
441
|
// country-based gating is enabled by default (reads Cloudflare geo
|
|
@@ -414,8 +445,8 @@ export default setIntlConfig({
|
|
|
414
445
|
// gdprCountries: [...], // defaults to EU/EEA + UK + Switzerland
|
|
415
446
|
// enableAnalyticsInDevMode: true, // analytics stay off in dev otherwise
|
|
416
447
|
// autoWireDialogs: false, // opt out and render the dialogs yourself
|
|
417
|
-
// dialogProps: { acceptText: "Accept" }, // forwarded to CookieConsentDialog
|
|
418
|
-
// updateDialogProps: { closeText: "Got it" }, // forwarded to PrivacyPolicyUpdateDialog
|
|
448
|
+
// dialogProps: { acceptText: "Accept", showPrivacyPolicy: true }, // forwarded to CookieConsentDialog
|
|
449
|
+
// updateDialogProps: { closeText: "Got it", showPrivacyPolicy: true }, // forwarded to PrivacyPolicyUpdateDialog
|
|
419
450
|
},
|
|
420
451
|
});
|
|
421
452
|
```
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cloudflare-next-intl",
|
|
3
|
+
"version": "0.8.53",
|
|
4
|
+
"description": "Optimized Next Intl Package Special for App Router and Cloudflare",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"bin": {
|
|
9
|
+
"cfni-db-codegen": "bin/db_codegen.mjs",
|
|
10
|
+
"cfni-db-install-exec": "bin/db_install_exec.mjs"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"bin",
|
|
15
|
+
"supabase",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"README.md",
|
|
18
|
+
"llms.txt"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./client": {
|
|
26
|
+
"types": "./dist/src/client/index.d.ts",
|
|
27
|
+
"import": "./dist/src/client/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./server": {
|
|
30
|
+
"types": "./dist/src/server/index.d.ts",
|
|
31
|
+
"import": "./dist/src/server/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./geo": {
|
|
34
|
+
"types": "./dist/src/server/functions/geo.d.ts",
|
|
35
|
+
"import": "./dist/src/server/functions/geo.js"
|
|
36
|
+
},
|
|
37
|
+
"./getCountry": {
|
|
38
|
+
"types": "./dist/src/server/functions/geo.d.ts",
|
|
39
|
+
"import": "./dist/src/server/functions/geo.js"
|
|
40
|
+
},
|
|
41
|
+
"./getTimezone": {
|
|
42
|
+
"types": "./dist/src/server/functions/geo.d.ts",
|
|
43
|
+
"import": "./dist/src/server/functions/geo.js"
|
|
44
|
+
},
|
|
45
|
+
"./middleware": {
|
|
46
|
+
"types": "./dist/src/config/middleware.d.ts",
|
|
47
|
+
"import": "./dist/src/config/middleware.js"
|
|
48
|
+
},
|
|
49
|
+
"./setIntlConfig": {
|
|
50
|
+
"types": "./dist/src/config/init_config.d.ts",
|
|
51
|
+
"import": "./dist/src/config/init_config.js"
|
|
52
|
+
},
|
|
53
|
+
"./serverProvider": {
|
|
54
|
+
"types": "./dist/src/server/components/server_provider.d.ts",
|
|
55
|
+
"import": "./dist/src/server/components/server_provider.js"
|
|
56
|
+
},
|
|
57
|
+
"./serverProviderStatic": {
|
|
58
|
+
"types": "./dist/src/server/components/server_provider_static.d.ts",
|
|
59
|
+
"import": "./dist/src/server/components/server_provider_static.js"
|
|
60
|
+
},
|
|
61
|
+
"./Link": {
|
|
62
|
+
"types": "./dist/src/server/components/link.d.ts",
|
|
63
|
+
"import": "./dist/src/server/components/link.js"
|
|
64
|
+
},
|
|
65
|
+
"./IntlHelperScript": {
|
|
66
|
+
"types": "./dist/src/server/components/helper_script.d.ts",
|
|
67
|
+
"import": "./dist/src/server/components/helper_script.js"
|
|
68
|
+
},
|
|
69
|
+
"./LocaleLink": {
|
|
70
|
+
"types": "./dist/src/client/components/locale_link.d.ts",
|
|
71
|
+
"import": "./dist/src/client/components/locale_link.js"
|
|
72
|
+
},
|
|
73
|
+
"./usePathname": {
|
|
74
|
+
"types": "./dist/src/client/hooks/use_path_name.d.ts",
|
|
75
|
+
"import": "./dist/src/client/hooks/use_path_name.js"
|
|
76
|
+
},
|
|
77
|
+
"./metadata": {
|
|
78
|
+
"types": "./dist/src/general/metadata.d.ts",
|
|
79
|
+
"import": "./dist/src/general/metadata.js"
|
|
80
|
+
},
|
|
81
|
+
"./setCookieClient": {
|
|
82
|
+
"types": "./dist/src/client/functions/set_cookie.d.ts",
|
|
83
|
+
"import": "./dist/src/client/functions/set_cookie.js"
|
|
84
|
+
},
|
|
85
|
+
"./getCookieClient": {
|
|
86
|
+
"types": "./dist/src/client/functions/get_cookie.d.ts",
|
|
87
|
+
"import": "./dist/src/client/functions/get_cookie.js"
|
|
88
|
+
},
|
|
89
|
+
"./localeStaticParams": {
|
|
90
|
+
"types": "./dist/src/server/functions/locale_static_params.d.ts",
|
|
91
|
+
"import": "./dist/src/server/functions/locale_static_params.js"
|
|
92
|
+
},
|
|
93
|
+
"./use": {
|
|
94
|
+
"react-server": {
|
|
95
|
+
"types": "./dist/src/server/functions/use_functions.d.ts",
|
|
96
|
+
"import": "./dist/src/server/functions/use_functions.js"
|
|
97
|
+
},
|
|
98
|
+
"default": {
|
|
99
|
+
"types": "./dist/src/client/hooks/client_hooks.d.ts",
|
|
100
|
+
"import": "./dist/src/client/hooks/client_hooks.js"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"./ThemeSwitcher": {
|
|
104
|
+
"types": "./dist/src/theme_switcher/components/theme_switcher.d.ts",
|
|
105
|
+
"import": "./dist/src/theme_switcher/components/theme_switcher.js"
|
|
106
|
+
},
|
|
107
|
+
"./firebaseAuthClient": {
|
|
108
|
+
"types": "./dist/src/firebase_auth/client/firebase_client.d.ts",
|
|
109
|
+
"import": "./dist/src/firebase_auth/client/firebase_client.js"
|
|
110
|
+
},
|
|
111
|
+
"./firebaseAuthClientProvider": {
|
|
112
|
+
"types": "./dist/src/firebase_auth/client/auth_user_provider.d.ts",
|
|
113
|
+
"import": "./dist/src/firebase_auth/client/auth_user_provider.js"
|
|
114
|
+
},
|
|
115
|
+
"./firebaseAuthServerProvider": {
|
|
116
|
+
"types": "./dist/src/firebase_auth/server/auth_user_server_provider.d.ts",
|
|
117
|
+
"import": "./dist/src/firebase_auth/server/auth_user_server_provider.js"
|
|
118
|
+
},
|
|
119
|
+
"./useFirebaseAuthUser": {
|
|
120
|
+
"react-server": {
|
|
121
|
+
"types": "./dist/src/firebase_auth/server/use_auth_user_server.d.ts",
|
|
122
|
+
"import": "./dist/src/firebase_auth/server/use_auth_user_server.js"
|
|
123
|
+
},
|
|
124
|
+
"default": {
|
|
125
|
+
"types": "./dist/src/firebase_auth/client/use_auth_user.d.ts",
|
|
126
|
+
"import": "./dist/src/firebase_auth/client/use_auth_user.js"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"./getFirebaseAuthUser": {
|
|
130
|
+
"types": "./dist/src/firebase_auth/server/use_auth_user_server.d.ts",
|
|
131
|
+
"import": "./dist/src/firebase_auth/server/use_auth_user_server.js"
|
|
132
|
+
},
|
|
133
|
+
"./firebaseAuthActions": {
|
|
134
|
+
"types": "./dist/src/firebase_auth/client/auth_actions.d.ts",
|
|
135
|
+
"import": "./dist/src/firebase_auth/client/auth_actions.js"
|
|
136
|
+
},
|
|
137
|
+
"./firebaseAuthMiddleware": {
|
|
138
|
+
"types": "./dist/src/firebase_auth/middleware/update_session.d.ts",
|
|
139
|
+
"import": "./dist/src/firebase_auth/middleware/update_session.js"
|
|
140
|
+
},
|
|
141
|
+
"./cookieConsent": {
|
|
142
|
+
"types": "./dist/src/cookie_consent/index.d.ts",
|
|
143
|
+
"import": "./dist/src/cookie_consent/index.js"
|
|
144
|
+
},
|
|
145
|
+
"./CookieConsentProvider": {
|
|
146
|
+
"types": "./dist/src/cookie_consent/client/cookie_consent_provider.d.ts",
|
|
147
|
+
"import": "./dist/src/cookie_consent/client/cookie_consent_provider.js"
|
|
148
|
+
},
|
|
149
|
+
"./useCookieConsent": {
|
|
150
|
+
"types": "./dist/src/cookie_consent/client/use_cookie_consent.d.ts",
|
|
151
|
+
"import": "./dist/src/cookie_consent/client/use_cookie_consent.js"
|
|
152
|
+
},
|
|
153
|
+
"./CookieConsentDialog": {
|
|
154
|
+
"types": "./dist/src/cookie_consent/client/components/cookie_consent_dialog.d.ts",
|
|
155
|
+
"import": "./dist/src/cookie_consent/client/components/cookie_consent_dialog.js"
|
|
156
|
+
},
|
|
157
|
+
"./PrivacyPolicyUpdateDialog": {
|
|
158
|
+
"types": "./dist/src/cookie_consent/client/components/privacy_policy_update_dialog.d.ts",
|
|
159
|
+
"import": "./dist/src/cookie_consent/client/components/privacy_policy_update_dialog.js"
|
|
160
|
+
},
|
|
161
|
+
"./cookieConsentAnalytics": {
|
|
162
|
+
"types": "./dist/src/cookie_consent/client/components/cookie_consent_analytics.d.ts",
|
|
163
|
+
"import": "./dist/src/cookie_consent/client/components/cookie_consent_analytics.js"
|
|
164
|
+
},
|
|
165
|
+
"./errorHandling": {
|
|
166
|
+
"types": "./dist/src/error_handling/index.d.ts",
|
|
167
|
+
"import": "./dist/src/error_handling/index.js"
|
|
168
|
+
},
|
|
169
|
+
"./installConsoleErrorOverride": {
|
|
170
|
+
"types": "./dist/src/error_handling/install_console_error_override.d.ts",
|
|
171
|
+
"import": "./dist/src/error_handling/install_console_error_override.js"
|
|
172
|
+
},
|
|
173
|
+
"./installGlobalErrorOverride": {
|
|
174
|
+
"types": "./dist/src/error_handling/install_global_error_override.d.ts",
|
|
175
|
+
"import": "./dist/src/error_handling/install_global_error_override.js"
|
|
176
|
+
},
|
|
177
|
+
"./stringifyUnknown": {
|
|
178
|
+
"types": "./dist/src/error_handling/stringify_unknown.d.ts",
|
|
179
|
+
"import": "./dist/src/error_handling/stringify_unknown.js"
|
|
180
|
+
},
|
|
181
|
+
"./formatErrorMessage": {
|
|
182
|
+
"types": "./dist/src/error_handling/format_error_message.d.ts",
|
|
183
|
+
"import": "./dist/src/error_handling/format_error_message.js"
|
|
184
|
+
},
|
|
185
|
+
"./defaultIgnoredConsoleErrors": {
|
|
186
|
+
"types": "./dist/src/error_handling/default_ignored_console_errors.d.ts",
|
|
187
|
+
"import": "./dist/src/error_handling/default_ignored_console_errors.js"
|
|
188
|
+
},
|
|
189
|
+
"./createServerErrorAction": {
|
|
190
|
+
"types": "./dist/src/error_handling/create_server_error_action.d.ts",
|
|
191
|
+
"import": "./dist/src/error_handling/create_server_error_action.js"
|
|
192
|
+
},
|
|
193
|
+
"./isStaleDeployError": {
|
|
194
|
+
"types": "./dist/src/error_handling/is_stale_deploy_error.d.ts",
|
|
195
|
+
"import": "./dist/src/error_handling/is_stale_deploy_error.js"
|
|
196
|
+
},
|
|
197
|
+
"./clearClientCache": {
|
|
198
|
+
"types": "./dist/src/error_handling/clear_client_cache.d.ts",
|
|
199
|
+
"import": "./dist/src/error_handling/clear_client_cache.js"
|
|
200
|
+
},
|
|
201
|
+
"./db": {
|
|
202
|
+
"types": "./dist/src/db/index.d.ts",
|
|
203
|
+
"import": "./dist/src/db/index.js"
|
|
204
|
+
},
|
|
205
|
+
"./dbEslint": {
|
|
206
|
+
"types": "./dist/src/db/eslint_config.d.ts",
|
|
207
|
+
"import": "./dist/src/db/eslint_config.js"
|
|
208
|
+
},
|
|
209
|
+
"./dbHelpers": {
|
|
210
|
+
"types": "./dist/src/db/helpers.d.ts",
|
|
211
|
+
"import": "./dist/src/db/helpers.js"
|
|
212
|
+
},
|
|
213
|
+
"./dbTesting": {
|
|
214
|
+
"types": "./dist/src/db/testing.d.ts",
|
|
215
|
+
"import": "./dist/src/db/testing.js"
|
|
216
|
+
},
|
|
217
|
+
"./dbSchema": {
|
|
218
|
+
"types": "./dist/src/db/schema.d.ts",
|
|
219
|
+
"import": "./dist/src/db/schema.js"
|
|
220
|
+
},
|
|
221
|
+
"./vite": {
|
|
222
|
+
"types": "./dist/src/vite/index.d.ts",
|
|
223
|
+
"import": "./dist/src/vite/index.js"
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
"scripts": {
|
|
227
|
+
"test": "vitest run --coverage",
|
|
228
|
+
"bench": "vitest bench --run",
|
|
229
|
+
"build": "tsc"
|
|
230
|
+
},
|
|
231
|
+
"repository": {
|
|
232
|
+
"type": "git",
|
|
233
|
+
"url": "git+https://github.com/demian-ilnytskyi/cloudflare-next-intl.git"
|
|
234
|
+
},
|
|
235
|
+
"keywords": [
|
|
236
|
+
"nextjs",
|
|
237
|
+
"i18n",
|
|
238
|
+
"internationalization",
|
|
239
|
+
"localization",
|
|
240
|
+
"multilingual",
|
|
241
|
+
"translation",
|
|
242
|
+
"language",
|
|
243
|
+
"optimized",
|
|
244
|
+
"performance",
|
|
245
|
+
"bundle-size",
|
|
246
|
+
"fast",
|
|
247
|
+
"efficient",
|
|
248
|
+
"app-router",
|
|
249
|
+
"server-components",
|
|
250
|
+
"ssr",
|
|
251
|
+
"ssg",
|
|
252
|
+
"tree-shaking",
|
|
253
|
+
"dynamic-imports",
|
|
254
|
+
"react",
|
|
255
|
+
"next-intl",
|
|
256
|
+
"messages",
|
|
257
|
+
"formatting",
|
|
258
|
+
"icu"
|
|
259
|
+
],
|
|
260
|
+
"author": "Demian Ilnutskyi",
|
|
261
|
+
"license": "MIT",
|
|
262
|
+
"bugs": {
|
|
263
|
+
"url": "https://github.com/demian-ilnytskyi/cloudflare-next-intl/issues"
|
|
264
|
+
},
|
|
265
|
+
"homepage": "https://github.com/demian-ilnytskyi/cloudflare-next-intl#readme",
|
|
266
|
+
"dependencies": {
|
|
267
|
+
"@microsoft/clarity": "^1.0.2",
|
|
268
|
+
"@supabase/supabase-js": "^2.112.3",
|
|
269
|
+
"drizzle-kit": "^0.31.10",
|
|
270
|
+
"drizzle-orm": "^0.45.2",
|
|
271
|
+
"embedded-postgres": "^18.4.0-beta.17",
|
|
272
|
+
"firebase": "^12.17.0",
|
|
273
|
+
"jose": "^6.2.8",
|
|
274
|
+
"pg": "^8.23.0"
|
|
275
|
+
},
|
|
276
|
+
"peerDependencies": {
|
|
277
|
+
"next": ">=12.0.0",
|
|
278
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0",
|
|
279
|
+
"typescript": ">=5.0.0",
|
|
280
|
+
"vite": ">=6"
|
|
281
|
+
},
|
|
282
|
+
"peerDependenciesMeta": {
|
|
283
|
+
"typescript": {
|
|
284
|
+
"optional": true
|
|
285
|
+
},
|
|
286
|
+
"vite": {
|
|
287
|
+
"optional": true
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"devDependencies": {
|
|
291
|
+
"@eslint/eslintrc": "^3",
|
|
292
|
+
"@eslint/js": "^9.27.0",
|
|
293
|
+
"@testing-library/dom": "^10.4.1",
|
|
294
|
+
"@testing-library/jest-dom": "^7.0.0",
|
|
295
|
+
"@testing-library/react": "^16.3.2",
|
|
296
|
+
"@types/node": "^20.14.5",
|
|
297
|
+
"@types/pg": "^8.23.1",
|
|
298
|
+
"@types/react": "^19.0.0",
|
|
299
|
+
"@types/react-dom": "^19.0.0",
|
|
300
|
+
"@vitest/coverage-v8": "^3.2.7",
|
|
301
|
+
"eslint": "^9.28.0",
|
|
302
|
+
"eslint-config-next": "15.3.3",
|
|
303
|
+
"eslint-config-prettier": "^10.1.2",
|
|
304
|
+
"jsdom": "^29.1.1",
|
|
305
|
+
"next": "^15.3.0",
|
|
306
|
+
"react": "19.2.0",
|
|
307
|
+
"react-dom": "19.2.0",
|
|
308
|
+
"tsup": "^8.5.1",
|
|
309
|
+
"typescript": "^5.5.3",
|
|
310
|
+
"typescript-eslint": "^8.33.1",
|
|
311
|
+
"vite": "^7.0.0",
|
|
312
|
+
"vitest": "^3.0.8"
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -87,6 +87,23 @@ export default async function intlMiddleware(request, options) {
|
|
|
87
87
|
pathWithoutLocale = pathname;
|
|
88
88
|
}
|
|
89
89
|
const effectiveLocaleForRequest = urlLocale ?? initialChosenLocale;
|
|
90
|
+
const country = request.cf?.country ?? request.headers.get('cf-ipcountry') ?? request.headers.get('x-cf-country');
|
|
91
|
+
if (country) {
|
|
92
|
+
request.headers.set('x-cf-country', country);
|
|
93
|
+
}
|
|
94
|
+
const timezone = request.cf?.timezone ?? request.headers.get('cf-timezone') ?? request.headers.get('x-cf-timezone');
|
|
95
|
+
if (timezone) {
|
|
96
|
+
request.headers.set('x-cf-timezone', timezone);
|
|
97
|
+
}
|
|
98
|
+
const requestHeaders = new Headers(request.headers);
|
|
99
|
+
requestHeaders.set('x-pathname', pathWithoutLocale);
|
|
100
|
+
requestHeaders.set('x-search', search);
|
|
101
|
+
if (country) {
|
|
102
|
+
requestHeaders.set('x-cf-country', country);
|
|
103
|
+
}
|
|
104
|
+
if (timezone) {
|
|
105
|
+
requestHeaders.set('x-cf-timezone', timezone);
|
|
106
|
+
}
|
|
90
107
|
let response;
|
|
91
108
|
let isRedirect = false;
|
|
92
109
|
let rewriteUrl;
|
|
@@ -96,7 +113,7 @@ export default async function intlMiddleware(request, options) {
|
|
|
96
113
|
const localeUrl = new URL(`${targetPath}${search}${hash}`, request.url);
|
|
97
114
|
if (initialChosenLocale === config.defaultLocale) {
|
|
98
115
|
rewriteUrl = localeUrl;
|
|
99
|
-
response = NextResponse.rewrite(localeUrl, { request });
|
|
116
|
+
response = NextResponse.rewrite(localeUrl, { request: { headers: requestHeaders } });
|
|
100
117
|
}
|
|
101
118
|
else {
|
|
102
119
|
isRedirect = true;
|
|
@@ -106,7 +123,9 @@ export default async function intlMiddleware(request, options) {
|
|
|
106
123
|
}
|
|
107
124
|
else {
|
|
108
125
|
response = NextResponse.next({
|
|
109
|
-
request
|
|
126
|
+
request: {
|
|
127
|
+
headers: requestHeaders,
|
|
128
|
+
},
|
|
110
129
|
});
|
|
111
130
|
}
|
|
112
131
|
if (options?.middlewareHandler && (!isRedirect || options.runHandlerOnRedirect)) {
|
|
@@ -130,14 +149,10 @@ export default async function intlMiddleware(request, options) {
|
|
|
130
149
|
response.headers.set('Content-Language', effectiveLocaleForRequest);
|
|
131
150
|
response.headers.set('x-pathname', pathWithoutLocale);
|
|
132
151
|
response.headers.set('x-search', search);
|
|
133
|
-
const country = request.cf?.country ?? request.headers.get('cf-ipcountry') ?? request.headers.get('x-cf-country');
|
|
134
152
|
if (country) {
|
|
135
|
-
request.headers.set('x-cf-country', country);
|
|
136
153
|
response.headers.set('x-cf-country', country);
|
|
137
154
|
}
|
|
138
|
-
const timezone = request.cf?.timezone ?? request.headers.get('cf-timezone') ?? request.headers.get('x-cf-timezone');
|
|
139
155
|
if (timezone) {
|
|
140
|
-
request.headers.set('x-cf-timezone', timezone);
|
|
141
156
|
response.headers.set('x-cf-timezone', timezone);
|
|
142
157
|
}
|
|
143
158
|
// Auto-wires the firebase_auth submodule's redirect/session-refresh
|
|
@@ -11,6 +11,11 @@ export interface CookieConsentDialogProps {
|
|
|
11
11
|
link?: React.ReactNode;
|
|
12
12
|
/** Label for the default privacy-policy link. Ignored when `link` is set. */
|
|
13
13
|
privacyPolicyLinkText?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Whether to show the privacy policy link. Defaults to `cookieConsent.showPrivacyPolicy`
|
|
16
|
+
* (or `true` if unconfigured). Pass `false` to hide it, or `true` to force show.
|
|
17
|
+
*/
|
|
18
|
+
showPrivacyPolicy?: boolean;
|
|
14
19
|
acceptText?: string;
|
|
15
20
|
declineText?: string;
|
|
16
21
|
/** Hides the decline ("necessary only") button, leaving only accept. */
|
|
@@ -32,4 +37,4 @@ export interface CookieConsentDialogProps {
|
|
|
32
37
|
* `render` (full custom markup) — none of it is hardcoded to Tailwind or any
|
|
33
38
|
* particular design system.
|
|
34
39
|
*/
|
|
35
|
-
export default function CookieConsentDialog({ message, link, privacyPolicyLinkText, acceptText, declineText, hideDecline, id, classNames, styles, render, }: CookieConsentDialogProps): React.ReactElement | null;
|
|
40
|
+
export default function CookieConsentDialog({ message, link, privacyPolicyLinkText, showPrivacyPolicy, acceptText, declineText, hideDecline, id, classNames, styles, render, }: CookieConsentDialogProps): React.ReactElement | null;
|
|
@@ -12,8 +12,8 @@ import { defaultCookieDialogClassNames } from './default_dialog_styles';
|
|
|
12
12
|
* `render` (full custom markup) — none of it is hardcoded to Tailwind or any
|
|
13
13
|
* particular design system.
|
|
14
14
|
*/
|
|
15
|
-
export default function CookieConsentDialog({ message, link, privacyPolicyLinkText, acceptText, declineText, hideDecline = false, id = 'cookie-consent-dialog', classNames, styles, render, }) {
|
|
16
|
-
const { consent, requiresConsent, isMounted, setConsent, privacyPolicyPath } = useCookieConsent();
|
|
15
|
+
export default function CookieConsentDialog({ message, link, privacyPolicyLinkText, showPrivacyPolicy, acceptText, declineText, hideDecline = false, id = 'cookie-consent-dialog', classNames, styles, render, }) {
|
|
16
|
+
const { consent, requiresConsent, isMounted, setConsent, privacyPolicyPath, showPrivacyPolicy: showPrivacyPolicyCtx } = useCookieConsent();
|
|
17
17
|
if (!isMounted || !requiresConsent || consent !== null)
|
|
18
18
|
return null;
|
|
19
19
|
if (render)
|
|
@@ -24,8 +24,11 @@ export default function CookieConsentDialog({ message, link, privacyPolicyLinkTe
|
|
|
24
24
|
const resolvedAcceptText = acceptText ?? text.acceptText;
|
|
25
25
|
const resolvedDeclineText = declineText ?? text.declineText;
|
|
26
26
|
const resolvedClassNames = { ...defaultCookieDialogClassNames, ...classNames };
|
|
27
|
+
const shouldShowPolicy = showPrivacyPolicy ?? showPrivacyPolicyCtx;
|
|
27
28
|
const resolvedLink = link !== undefined
|
|
28
29
|
? link
|
|
29
|
-
:
|
|
30
|
+
: (shouldShowPolicy && privacyPolicyPath !== false)
|
|
31
|
+
? _jsx(DefaultPrivacyPolicyLink, { privacyPolicyPath: privacyPolicyPath, text: resolvedPrivacyPolicyLinkText, className: resolvedClassNames.link })
|
|
32
|
+
: null;
|
|
30
33
|
return (_jsx(DialogPortal, { children: _jsxs("div", { id: id, role: "dialog", "aria-modal": "false", "aria-labelledby": `${id}-title`, className: resolvedClassNames.root, style: styles?.root, children: [_jsxs("p", { id: `${id}-title`, className: resolvedClassNames.message, style: styles?.message, children: [resolvedMessage, resolvedLink ? _jsxs("span", { children: [" ", resolvedLink] }) : null] }), _jsxs("div", { className: resolvedClassNames.actions, style: styles?.actions, children: [!hideDecline && (_jsx("button", { type: "button", onClick: () => setConsent(false), className: resolvedClassNames.declineButton, style: styles?.declineButton, children: resolvedDeclineText })), _jsx("button", { type: "button", onClick: () => setConsent(true), className: resolvedClassNames.acceptButton, style: styles?.acceptButton, children: resolvedAcceptText })] })] }) }));
|
|
31
34
|
}
|
|
@@ -10,6 +10,11 @@ export interface PrivacyPolicyUpdateDialogProps {
|
|
|
10
10
|
link?: React.ReactNode;
|
|
11
11
|
/** Label for the default privacy-policy link. Ignored when `link` is set. */
|
|
12
12
|
privacyPolicyLinkText?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Whether to show the privacy policy link. Defaults to `cookieConsent.showPrivacyPolicy`
|
|
15
|
+
* (or `true` if unconfigured). Pass `false` to hide it, or `true` to force show.
|
|
16
|
+
*/
|
|
17
|
+
showPrivacyPolicy?: boolean;
|
|
13
18
|
closeText?: string;
|
|
14
19
|
id?: string;
|
|
15
20
|
classNames?: CookieDialogClassNames;
|
|
@@ -28,4 +33,4 @@ export interface PrivacyPolicyUpdateDialogProps {
|
|
|
28
33
|
* `null` otherwise, or once acknowledged. Every visual aspect is overridable
|
|
29
34
|
* via `classNames`/`styles` (per-slot) or `render` (full custom markup).
|
|
30
35
|
*/
|
|
31
|
-
export default function PrivacyPolicyUpdateDialog({ message, link, privacyPolicyLinkText, closeText, id, classNames, styles, render, }: PrivacyPolicyUpdateDialogProps): React.ReactElement | null;
|
|
36
|
+
export default function PrivacyPolicyUpdateDialog({ message, link, privacyPolicyLinkText, showPrivacyPolicy, closeText, id, classNames, styles, render, }: PrivacyPolicyUpdateDialogProps): React.ReactElement | null;
|
|
@@ -12,8 +12,8 @@ import { defaultCookieDialogClassNames } from './default_dialog_styles';
|
|
|
12
12
|
* `null` otherwise, or once acknowledged. Every visual aspect is overridable
|
|
13
13
|
* via `classNames`/`styles` (per-slot) or `render` (full custom markup).
|
|
14
14
|
*/
|
|
15
|
-
export default function PrivacyPolicyUpdateDialog({ message, link, privacyPolicyLinkText, closeText, id = 'privacy-policy-update-dialog', classNames, styles, render, }) {
|
|
16
|
-
const { privacyPolicyUpdated, acknowledgePrivacyPolicyUpdate, privacyPolicyPath } = useCookieConsent();
|
|
15
|
+
export default function PrivacyPolicyUpdateDialog({ message, link, privacyPolicyLinkText, showPrivacyPolicy, closeText, id = 'privacy-policy-update-dialog', classNames, styles, render, }) {
|
|
16
|
+
const { privacyPolicyUpdated, acknowledgePrivacyPolicyUpdate, privacyPolicyPath, showPrivacyPolicy: showPrivacyPolicyCtx } = useCookieConsent();
|
|
17
17
|
if (!privacyPolicyUpdated)
|
|
18
18
|
return null;
|
|
19
19
|
if (render)
|
|
@@ -23,8 +23,11 @@ export default function PrivacyPolicyUpdateDialog({ message, link, privacyPolicy
|
|
|
23
23
|
const resolvedPrivacyPolicyLinkText = privacyPolicyLinkText ?? text.privacyPolicyLinkText;
|
|
24
24
|
const resolvedCloseText = closeText ?? text.closeText;
|
|
25
25
|
const resolvedClassNames = { ...defaultCookieDialogClassNames, ...classNames };
|
|
26
|
+
const shouldShowPolicy = showPrivacyPolicy ?? showPrivacyPolicyCtx;
|
|
26
27
|
const resolvedLink = link !== undefined
|
|
27
28
|
? link
|
|
28
|
-
:
|
|
29
|
+
: (shouldShowPolicy && privacyPolicyPath !== false)
|
|
30
|
+
? _jsx(DefaultPrivacyPolicyLink, { privacyPolicyPath: privacyPolicyPath, text: resolvedPrivacyPolicyLinkText, className: resolvedClassNames.link })
|
|
31
|
+
: null;
|
|
29
32
|
return (_jsx(DialogPortal, { children: _jsxs("div", { id: id, role: "dialog", "aria-modal": "false", "aria-labelledby": `${id}-title`, className: resolvedClassNames.root, style: styles?.root, children: [_jsxs("p", { id: `${id}-title`, className: resolvedClassNames.message, style: styles?.message, children: [resolvedMessage, resolvedLink ? _jsxs("span", { children: [" ", resolvedLink] }) : null] }), _jsx("button", { type: "button", onClick: acknowledgePrivacyPolicyUpdate, "aria-label": resolvedCloseText, className: resolvedClassNames.closeButton, style: styles?.closeButton, children: resolvedCloseText })] }) }));
|
|
30
33
|
}
|
|
@@ -49,7 +49,7 @@ function parseConsent(raw) {
|
|
|
49
49
|
* ```
|
|
50
50
|
*/
|
|
51
51
|
export default function CookieConsentProvider({ requiresConsent = true, children }) {
|
|
52
|
-
const { consentCookieName, dateCookieName, maxAge, policyDate, privacyPolicyPath } = useMemo(() => {
|
|
52
|
+
const { consentCookieName, dateCookieName, maxAge, policyDate, privacyPolicyPath, showPrivacyPolicy } = useMemo(() => {
|
|
53
53
|
const cc = requireCookieConsentConfig(config.cookieConsent);
|
|
54
54
|
return {
|
|
55
55
|
consentCookieName: cc.consentCookieName ?? cookieConsentCookieKey,
|
|
@@ -57,6 +57,7 @@ export default function CookieConsentProvider({ requiresConsent = true, children
|
|
|
57
57
|
maxAge: cc.cookieMaxAge ?? 31536000,
|
|
58
58
|
policyDate: cc.privacyPolicyDate ? new Date(cc.privacyPolicyDate) : null,
|
|
59
59
|
privacyPolicyPath: cc.privacyPolicyPath ?? '/privacy-policy',
|
|
60
|
+
showPrivacyPolicy: cc.showPrivacyPolicy ?? true,
|
|
60
61
|
};
|
|
61
62
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
62
63
|
}, []);
|
|
@@ -120,6 +121,6 @@ export default function CookieConsentProvider({ requiresConsent = true, children
|
|
|
120
121
|
acknowledgePrivacyPolicyUpdate();
|
|
121
122
|
}
|
|
122
123
|
}, [pathname, privacyPolicyUpdated, privacyPolicyPath, acknowledgePrivacyPolicyUpdate]);
|
|
123
|
-
const contextValue = useMemo(() => ({ consent, requiresConsent, privacyPolicyUpdated, isMounted, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath }), [consent, requiresConsent, privacyPolicyUpdated, isMounted, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath]);
|
|
124
|
+
const contextValue = useMemo(() => ({ consent, requiresConsent, privacyPolicyUpdated, isMounted, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath, showPrivacyPolicy }), [consent, requiresConsent, privacyPolicyUpdated, isMounted, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath, showPrivacyPolicy]);
|
|
124
125
|
return (_jsx(CookieConsentContext.Provider, { value: contextValue, children: children }));
|
|
125
126
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { CookieConsentGetCloudflareContext, ErrorHandlingRoutingConfig } from '../types/types';
|
|
1
|
+
import type { CookieConsentGetCloudflareContext, ErrorHandlingRoutingConfig, GenerateRoutingConfig } from '../types/types';
|
|
2
2
|
/**
|
|
3
3
|
* Default `cookieConsent.gdprCountries` — EU/EEA member states (GDPR),
|
|
4
4
|
* Iceland/Liechtenstein/Norway (EEA), the UK (UK-GDPR), and Switzerland
|
|
5
5
|
* (nFADP). ISO 3166-1 alpha-2.
|
|
6
6
|
*/
|
|
7
7
|
export declare const defaultGdprCountries: readonly string[];
|
|
8
|
-
export default function resolveRequiresConsent(getCountryCode: (() => string | undefined | Promise<string | undefined>) | undefined, getCloudflareContext: CookieConsentGetCloudflareContext | undefined, gdprCountries: readonly string[] | undefined, errorHandlingConfig?: ErrorHandlingRoutingConfig, countryHeaderNames?: readonly string[]): Promise<boolean>;
|
|
8
|
+
export default function resolveRequiresConsent(getCountryCode: (() => string | undefined | Promise<string | undefined>) | undefined, getCloudflareContext: CookieConsentGetCloudflareContext | undefined, gdprCountries: readonly string[] | undefined, errorHandlingConfig?: ErrorHandlingRoutingConfig, countryHeaderNames?: readonly string[], generateConfig?: GenerateRoutingConfig): Promise<boolean>;
|
|
@@ -32,7 +32,7 @@ function getGdprCountriesSet(gdprCountries) {
|
|
|
32
32
|
}
|
|
33
33
|
return set;
|
|
34
34
|
}
|
|
35
|
-
export default async function resolveRequiresConsent(getCountryCode, getCloudflareContext, gdprCountries, errorHandlingConfig, countryHeaderNames) {
|
|
35
|
+
export default async function resolveRequiresConsent(getCountryCode, getCloudflareContext, gdprCountries, errorHandlingConfig, countryHeaderNames, generateConfig) {
|
|
36
36
|
let countryCode;
|
|
37
37
|
if (getCountryCode) {
|
|
38
38
|
countryCode = await getCountryCode();
|
|
@@ -51,7 +51,7 @@ export default async function resolveRequiresConsent(getCountryCode, getCloudfla
|
|
|
51
51
|
// headers off the current request.
|
|
52
52
|
if (typeof countryCode !== 'string' || !countryCode) {
|
|
53
53
|
try {
|
|
54
|
-
countryCode = await getCountry(undefined,
|
|
54
|
+
countryCode = await getCountry(undefined, generateConfig, countryHeaderNames);
|
|
55
55
|
}
|
|
56
56
|
catch {
|
|
57
57
|
return true;
|
|
@@ -43,6 +43,11 @@ export interface CookieConsentContextType {
|
|
|
43
43
|
* when their `link` prop is omitted.
|
|
44
44
|
*/
|
|
45
45
|
privacyPolicyPath: string | false;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the privacy policy link should be shown in default dialogs.
|
|
48
|
+
* Defaults to `true`.
|
|
49
|
+
*/
|
|
50
|
+
showPrivacyPolicy: boolean;
|
|
46
51
|
}
|
|
47
52
|
/** Slot-level style/class overrides accepted by the default dialog components. */
|
|
48
53
|
export interface CookieDialogClassNames {
|
|
@@ -144,7 +144,7 @@ export default async function LocationzationProvider({ language, messages, stati
|
|
|
144
144
|
// `getCloudflareContext` path in dev; fail-safe to `true`
|
|
145
145
|
// (banner shown) same as an unresolved country would.
|
|
146
146
|
requiresConsent = !isDevEnvironment
|
|
147
|
-
? await resolveRequiresConsent(config.cookieConsent.getCountryCode, config.generate?.getCloudflareContext, config.cookieConsent.gdprCountries, config.errorHandling, config.cookieConsent.countryHeaderNames)
|
|
147
|
+
? await resolveRequiresConsent(config.cookieConsent.getCountryCode, config.generate?.getCloudflareContext, config.cookieConsent.gdprCountries, config.errorHandling, config.cookieConsent.countryHeaderNames, config.generate)
|
|
148
148
|
: false;
|
|
149
149
|
const analyticsAllowedInEnv = config.cookieConsent.enableAnalyticsInDevMode === true || !isDevEnvironment;
|
|
150
150
|
if (config.cookieConsent.autoWireAnalytics !== false && analyticsAllowedInEnv) {
|
|
@@ -87,7 +87,7 @@ export default async function LocationzationProvider({ language, messages, child
|
|
|
87
87
|
// `getCloudflareContext` path in dev; fail-safe to `true`
|
|
88
88
|
// (banner shown) same as an unresolved country would.
|
|
89
89
|
requiresConsent = !isDevEnvironment
|
|
90
|
-
? await resolveRequiresConsent(config.cookieConsent.getCountryCode, config.generate?.getCloudflareContext, config.cookieConsent.gdprCountries, config.errorHandling, config.cookieConsent.countryHeaderNames)
|
|
90
|
+
? await resolveRequiresConsent(config.cookieConsent.getCountryCode, config.generate?.getCloudflareContext, config.cookieConsent.gdprCountries, config.errorHandling, config.cookieConsent.countryHeaderNames, config.generate)
|
|
91
91
|
: false;
|
|
92
92
|
const analyticsAllowedInEnv = config.cookieConsent.enableAnalyticsInDevMode === true || !isDevEnvironment;
|
|
93
93
|
if (config.cookieConsent.autoWireAnalytics !== false && analyticsAllowedInEnv) {
|
|
@@ -10,7 +10,7 @@ export declare const defaultTimezoneHeaderNames: readonly string[];
|
|
|
10
10
|
* 1. Explicit `input` (Request, NextRequest, or Headers) if provided
|
|
11
11
|
* 2. Next.js request headers via `headers()` (`headerNames`, default
|
|
12
12
|
* `x-cf-country`, `cf-ipcountry`)
|
|
13
|
-
* 3. `generate.getCloudflareContext` or `cf.country` if passed
|
|
13
|
+
* 3. `generate.ctx` or `generate.getCloudflareContext` or `cf.country` if passed
|
|
14
14
|
* 4. `undefined` if outside request scope or unavailable
|
|
15
15
|
*/
|
|
16
16
|
export declare function getCountry(input?: RequestOrHeaders, generate?: GenerateRoutingConfig, headerNames?: readonly string[]): Promise<string | undefined>;
|
|
@@ -21,7 +21,7 @@ export declare function getCountry(input?: RequestOrHeaders, generate?: Generate
|
|
|
21
21
|
* 1. Explicit `input` (Request, NextRequest, or Headers) if provided
|
|
22
22
|
* 2. Next.js request headers via `headers()` (`headerNames`, default
|
|
23
23
|
* `x-cf-timezone`, `cf-timezone`)
|
|
24
|
-
* 3. `generate.getCloudflareContext` or `cf.timezone` if passed
|
|
24
|
+
* 3. `generate.ctx` or `generate.getCloudflareContext` or `cf.timezone` if passed
|
|
25
25
|
* 4. `fallback` (or `undefined`) if outside request scope or unavailable
|
|
26
26
|
*/
|
|
27
27
|
export declare function getTimezone(input?: RequestOrHeaders, fallback?: string, generate?: GenerateRoutingConfig, headerNames?: readonly string[]): Promise<string | undefined>;
|
|
@@ -14,10 +14,10 @@ function extractHeader(h, name) {
|
|
|
14
14
|
// Read lazily (and tolerantly): `@intl-config` may not be set at all in
|
|
15
15
|
// standalone/unit usage of these helpers, and importing the config eagerly
|
|
16
16
|
// would risk a cycle with a config module that itself imports from here.
|
|
17
|
-
async function
|
|
17
|
+
async function configuredGenerate() {
|
|
18
18
|
try {
|
|
19
19
|
const config = (await import('../../config/intl_config')).default;
|
|
20
|
-
return config?.generate
|
|
20
|
+
return config?.generate;
|
|
21
21
|
}
|
|
22
22
|
catch {
|
|
23
23
|
return undefined;
|
|
@@ -38,13 +38,13 @@ function extractFromHeaderNames(h, headerNames) {
|
|
|
38
38
|
* 1. Explicit `input` (Request, NextRequest, or Headers) if provided
|
|
39
39
|
* 2. Next.js request headers via `headers()` (`headerNames`, default
|
|
40
40
|
* `x-cf-country`, `cf-ipcountry`)
|
|
41
|
-
* 3. `generate.getCloudflareContext` or `cf.country` if passed
|
|
41
|
+
* 3. `generate.ctx` or `generate.getCloudflareContext` or `cf.country` if passed
|
|
42
42
|
* 4. `undefined` if outside request scope or unavailable
|
|
43
43
|
*/
|
|
44
44
|
export async function getCountry(input, generate, headerNames) {
|
|
45
|
+
const gen = generate ?? await configuredGenerate();
|
|
45
46
|
const names = headerNames
|
|
46
|
-
??
|
|
47
|
-
?? await configuredHeaderNames('countryHeaderNames')
|
|
47
|
+
?? gen?.countryHeaderNames
|
|
48
48
|
?? defaultCountryHeaderNames;
|
|
49
49
|
if (input) {
|
|
50
50
|
if ('headers' in input && input.headers) {
|
|
@@ -72,9 +72,21 @@ export async function getCountry(input, generate, headerNames) {
|
|
|
72
72
|
catch {
|
|
73
73
|
// Outside request scope / build time
|
|
74
74
|
}
|
|
75
|
-
if (
|
|
75
|
+
if (gen?.ctx) {
|
|
76
76
|
try {
|
|
77
|
-
const
|
|
77
|
+
const context = typeof gen.ctx === 'function' ? await gen.ctx() : gen.ctx;
|
|
78
|
+
const cf = context?.cf;
|
|
79
|
+
if (cf?.country && typeof cf.country === 'string' && cf.country.length > 0) {
|
|
80
|
+
return cf.country;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Ignore context resolution errors
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (gen?.getCloudflareContext) {
|
|
88
|
+
try {
|
|
89
|
+
const ctx = await gen.getCloudflareContext({ async: true });
|
|
78
90
|
if (ctx?.cf?.country && typeof ctx.cf.country === 'string' && ctx.cf.country.length > 0) {
|
|
79
91
|
return ctx.cf.country;
|
|
80
92
|
}
|
|
@@ -92,13 +104,13 @@ export async function getCountry(input, generate, headerNames) {
|
|
|
92
104
|
* 1. Explicit `input` (Request, NextRequest, or Headers) if provided
|
|
93
105
|
* 2. Next.js request headers via `headers()` (`headerNames`, default
|
|
94
106
|
* `x-cf-timezone`, `cf-timezone`)
|
|
95
|
-
* 3. `generate.getCloudflareContext` or `cf.timezone` if passed
|
|
107
|
+
* 3. `generate.ctx` or `generate.getCloudflareContext` or `cf.timezone` if passed
|
|
96
108
|
* 4. `fallback` (or `undefined`) if outside request scope or unavailable
|
|
97
109
|
*/
|
|
98
110
|
export async function getTimezone(input, fallback, generate, headerNames) {
|
|
111
|
+
const gen = generate ?? await configuredGenerate();
|
|
99
112
|
const names = headerNames
|
|
100
|
-
??
|
|
101
|
-
?? await configuredHeaderNames('timezoneHeaderNames')
|
|
113
|
+
?? gen?.timezoneHeaderNames
|
|
102
114
|
?? defaultTimezoneHeaderNames;
|
|
103
115
|
if (input) {
|
|
104
116
|
if ('headers' in input && input.headers) {
|
|
@@ -126,9 +138,21 @@ export async function getTimezone(input, fallback, generate, headerNames) {
|
|
|
126
138
|
catch {
|
|
127
139
|
// Outside request scope
|
|
128
140
|
}
|
|
129
|
-
if (
|
|
141
|
+
if (gen?.ctx) {
|
|
130
142
|
try {
|
|
131
|
-
const
|
|
143
|
+
const context = typeof gen.ctx === 'function' ? await gen.ctx() : gen.ctx;
|
|
144
|
+
const cf = context?.cf;
|
|
145
|
+
if (cf?.timezone && typeof cf.timezone === 'string' && cf.timezone.length > 0) {
|
|
146
|
+
return cf.timezone;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Ignore context resolution errors
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (gen?.getCloudflareContext) {
|
|
154
|
+
try {
|
|
155
|
+
const ctx = await gen.getCloudflareContext({ async: true });
|
|
132
156
|
if (ctx?.cf?.timezone && typeof ctx.cf.timezone === 'string' && ctx.cf.timezone.length > 0) {
|
|
133
157
|
return ctx.cf.timezone;
|
|
134
158
|
}
|
|
@@ -332,6 +332,12 @@ export interface CookieConsentRoutingConfig {
|
|
|
332
332
|
* overridable per-dialog via the `link` prop).
|
|
333
333
|
*/
|
|
334
334
|
privacyPolicyPath?: string | false;
|
|
335
|
+
/**
|
|
336
|
+
* Whether to show the privacy policy link in the default cookie consent
|
|
337
|
+
* dialog and privacy policy update dialog. Defaults to `true`.
|
|
338
|
+
* Set `false` to hide the privacy policy link by default.
|
|
339
|
+
*/
|
|
340
|
+
showPrivacyPolicy?: boolean;
|
|
335
341
|
/** Cookie-consent cookie name. Defaults to `'__cookie_consent_key__'`. */
|
|
336
342
|
consentCookieName?: string;
|
|
337
343
|
/** Privacy-policy-date cookie name. Defaults to `'__privacy_policy_date_key__'`. */
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
export declare const CF_WORKERS_CLIENT_STUB_ID = "\0cfni:cloudflare-workers-client-stub";
|
|
3
|
+
export declare const CF_WORKERS_CLIENT_STUB_CODE = "\nexport class WorkerEntrypoint {}\nexport class DurableObject {}\nexport const env = {};\nexport default {};\n";
|
|
4
|
+
export declare function cfWorkersClientStubPlugin(): Plugin;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const CF_WORKERS_CLIENT_STUB_ID = "\0cfni:cloudflare-workers-client-stub";
|
|
2
|
+
export const CF_WORKERS_CLIENT_STUB_CODE = `
|
|
3
|
+
export class WorkerEntrypoint {}
|
|
4
|
+
export class DurableObject {}
|
|
5
|
+
export const env = {};
|
|
6
|
+
export default {};
|
|
7
|
+
`;
|
|
8
|
+
export function cfWorkersClientStubPlugin() {
|
|
9
|
+
return {
|
|
10
|
+
name: "cfni:cf-workers-client-stub",
|
|
11
|
+
enforce: "pre",
|
|
12
|
+
resolveId(id, _importer, options) {
|
|
13
|
+
if (id === "cloudflare:workers" && (this.environment?.name === "client" || options?.ssr === false)) {
|
|
14
|
+
return CF_WORKERS_CLIENT_STUB_ID;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
load(id) {
|
|
18
|
+
if (id === CF_WORKERS_CLIENT_STUB_ID) {
|
|
19
|
+
return CF_WORKERS_CLIENT_STUB_CODE;
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { buildIdAsset } from "./build_id_asset.js";
|
|
2
|
+
export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
|
|
3
|
+
export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
|
|
4
|
+
export { localeFilePlugin, resolveDefaultIntlConfigPath, type LocaleFilePluginOptions } from "./locale_file_plugin.js";
|
|
5
|
+
export { cloudflareNextIntl, cloudflareNextIntlPlugin, type CloudflareNextIntlOptions, default } from "./plugin.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { buildIdAsset } from "./build_id_asset.js";
|
|
2
|
+
export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
|
|
3
|
+
export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
|
|
4
|
+
export { localeFilePlugin, resolveDefaultIntlConfigPath } from "./locale_file_plugin.js";
|
|
5
|
+
export { cloudflareNextIntl, cloudflareNextIntlPlugin, default } from "./plugin.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
export interface LocaleFilePluginOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Directory containing translation json files (e.g. `./messages`).
|
|
5
|
+
* @default "./messages"
|
|
6
|
+
*/
|
|
7
|
+
messagesDir?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Path to `intl_config.ts`.
|
|
10
|
+
*/
|
|
11
|
+
intlConfigPath?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Root directory of the project. Defaults to `process.cwd()`.
|
|
14
|
+
*/
|
|
15
|
+
root?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function resolveDefaultIntlConfigPath(root: string): string;
|
|
18
|
+
export declare function getCfniDistSrcDir(root: string): string;
|
|
19
|
+
export declare function localeFilePlugin(options?: LocaleFilePluginOptions): Plugin;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
export function resolveDefaultIntlConfigPath(root) {
|
|
5
|
+
const candidates = [
|
|
6
|
+
path.join(root, "src", "l18n", "intl_config.ts"),
|
|
7
|
+
path.join(root, "src", "l18n", "intl_config.js"),
|
|
8
|
+
path.join(root, "src", "intl_config.ts"),
|
|
9
|
+
path.join(root, "src", "intl_config.js"),
|
|
10
|
+
path.join(root, "intl_config.ts"),
|
|
11
|
+
path.join(root, "intl_config.js"),
|
|
12
|
+
];
|
|
13
|
+
for (const candidate of candidates) {
|
|
14
|
+
if (fs.existsSync(candidate)) {
|
|
15
|
+
return candidate;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return candidates[0];
|
|
19
|
+
}
|
|
20
|
+
export function getCfniDistSrcDir(root) {
|
|
21
|
+
try {
|
|
22
|
+
const require = createRequire(path.join(root, "package.json"));
|
|
23
|
+
const pkgEntry = require.resolve("cloudflare-next-intl");
|
|
24
|
+
const distDir = path.dirname(pkgEntry);
|
|
25
|
+
const distSrc = path.join(distDir, "src");
|
|
26
|
+
return distSrc.replace(/\\/g, "/");
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
const fallback = path.join(root, "node_modules", "cloudflare-next-intl", "dist", "src");
|
|
30
|
+
return fallback.replace(/\\/g, "/");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function localeFilePlugin(options = {}) {
|
|
34
|
+
let resolvedRoot = options.root ?? process.cwd();
|
|
35
|
+
let resolvedMessagesDir = options.messagesDir
|
|
36
|
+
? (path.isAbsolute(options.messagesDir) ? options.messagesDir : path.join(resolvedRoot, options.messagesDir))
|
|
37
|
+
: path.join(resolvedRoot, "messages");
|
|
38
|
+
let resolvedIntlConfigPath = options.intlConfigPath
|
|
39
|
+
? (path.isAbsolute(options.intlConfigPath) ? options.intlConfigPath : path.join(resolvedRoot, options.intlConfigPath))
|
|
40
|
+
: resolveDefaultIntlConfigPath(resolvedRoot);
|
|
41
|
+
return {
|
|
42
|
+
name: "cfni:locale-file",
|
|
43
|
+
enforce: "pre",
|
|
44
|
+
configResolved(config) {
|
|
45
|
+
resolvedRoot = options.root ?? config.root ?? process.cwd();
|
|
46
|
+
if (!options.messagesDir) {
|
|
47
|
+
resolvedMessagesDir = path.join(resolvedRoot, "messages");
|
|
48
|
+
}
|
|
49
|
+
if (!options.intlConfigPath) {
|
|
50
|
+
resolvedIntlConfigPath = resolveDefaultIntlConfigPath(resolvedRoot);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
resolveId(id) {
|
|
54
|
+
if (id.startsWith("@locale-file/")) {
|
|
55
|
+
const file = id.replace("@locale-file/", "");
|
|
56
|
+
return path.join(resolvedMessagesDir, file);
|
|
57
|
+
}
|
|
58
|
+
if (id === "@intl-config") {
|
|
59
|
+
return resolvedIntlConfigPath;
|
|
60
|
+
}
|
|
61
|
+
if (id === "cloudflare-next-intl" && this.environment?.name === "rsc") {
|
|
62
|
+
return "\0cloudflare-next-intl:rsc";
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
load(id) {
|
|
66
|
+
if (id === "\0cloudflare-next-intl:rsc") {
|
|
67
|
+
const cfniDir = getCfniDistSrcDir(resolvedRoot);
|
|
68
|
+
return `
|
|
69
|
+
export * from '${cfniDir}/config/index.js';
|
|
70
|
+
export * from '${cfniDir}/general/index.js';
|
|
71
|
+
export * from '${cfniDir}/server/index.js';
|
|
72
|
+
export * from '${cfniDir}/theme_switcher/index.js';
|
|
73
|
+
export * from '${cfniDir}/types/index.js';
|
|
74
|
+
export * from '${cfniDir}/client/index.js';
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
transform(code, id) {
|
|
79
|
+
if (id.includes("cloudflare-next-intl") && code.includes("@locale-file")) {
|
|
80
|
+
const globPattern = `/messages/*.json`;
|
|
81
|
+
return {
|
|
82
|
+
code: `
|
|
83
|
+
const __cfni_locales__ = import.meta.glob('${globPattern}', { eager: true });
|
|
84
|
+
${code.replace(/\(await import\([`'"]@locale-file\/\$\{locale\}\.json[`'"]\)\)\.default/g, `(__cfni_locales__[\`/messages/\${locale}.json\`]?.default ?? (() => { throw new Error('missing locale'); })())`)}`,
|
|
85
|
+
map: null,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import { type LocaleFilePluginOptions } from "./locale_file_plugin.js";
|
|
3
|
+
export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Emit static `BUILD_ID` asset on client build for Vinext / Cloudflare.
|
|
6
|
+
* Set to `false` to disable or pass a custom filename string.
|
|
7
|
+
* @default true ("BUILD_ID")
|
|
8
|
+
*/
|
|
9
|
+
buildIdAsset?: boolean | string;
|
|
10
|
+
/**
|
|
11
|
+
* Enable `@locale-file/*` resolution, `@intl-config` alias, RSC re-exports,
|
|
12
|
+
* and eager glob bundling for messages (`import.meta.glob('/messages/*.json', { eager: true })`).
|
|
13
|
+
* @default true
|
|
14
|
+
*/
|
|
15
|
+
localeFiles?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Stub `next/dist/server/web/spec-extension/user-agent` to avoid pulling `node:fs` into Cloudflare Workers runtime.
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
userAgentStub?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Stub `cloudflare:workers` on client / non-SSR builds to prevent client bundling errors.
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
cfWorkersClientStub?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare function cloudflareNextIntl(options?: CloudflareNextIntlOptions): Plugin[];
|
|
28
|
+
export declare const cloudflareNextIntlPlugin: typeof cloudflareNextIntl;
|
|
29
|
+
export default cloudflareNextIntl;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { buildIdAsset } from "./build_id_asset.js";
|
|
2
|
+
import { userAgentStubPlugin } from "./user_agent_stub.js";
|
|
3
|
+
import { cfWorkersClientStubPlugin } from "./cf_workers_client_stub.js";
|
|
4
|
+
import { localeFilePlugin } from "./locale_file_plugin.js";
|
|
5
|
+
export function cloudflareNextIntl(options = {}) {
|
|
6
|
+
const plugins = [];
|
|
7
|
+
if (options.buildIdAsset !== false) {
|
|
8
|
+
const fileName = typeof options.buildIdAsset === "string" ? options.buildIdAsset : "BUILD_ID";
|
|
9
|
+
plugins.push(buildIdAsset(fileName));
|
|
10
|
+
}
|
|
11
|
+
if (options.cfWorkersClientStub !== false) {
|
|
12
|
+
plugins.push(cfWorkersClientStubPlugin());
|
|
13
|
+
}
|
|
14
|
+
if (options.userAgentStub !== false) {
|
|
15
|
+
plugins.push(userAgentStubPlugin());
|
|
16
|
+
}
|
|
17
|
+
if (options.localeFiles !== false) {
|
|
18
|
+
plugins.push(localeFilePlugin({
|
|
19
|
+
messagesDir: options.messagesDir,
|
|
20
|
+
intlConfigPath: options.intlConfigPath,
|
|
21
|
+
root: options.root,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
return plugins;
|
|
25
|
+
}
|
|
26
|
+
export const cloudflareNextIntlPlugin = cloudflareNextIntl;
|
|
27
|
+
export default cloudflareNextIntl;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
export declare const USER_AGENT_STUB_ID = "\0cfni:user-agent-stub";
|
|
3
|
+
export declare const USER_AGENT_STUB_CODE = "\nexport function isBot(input) {\n if (!input) return false;\n return /Googlebot|Mediapartners-Google|AdsBot-Google|googleweblight|Storebot-Google|Google-PageRenderer|Google-InspectionTool|Bingbot|BingPreview|Slurp|DuckDuckBot|baiduspider|yandex|sogou|LinkedInBot|bitlybot|tumblr|vkShare|quora link preview|facebookexternalhit|facebookcatalog|Twitterbot|applebot|redditbot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|ia_archiver|GPTBot/i.test(\n input\n );\n}\n\nexport function userAgentFromString(input) {\n return {\n ua: input ?? \"\",\n browser: { name: undefined, version: undefined, major: undefined },\n cpu: { architecture: undefined },\n device: { model: undefined, type: undefined, vendor: undefined },\n engine: { name: undefined, version: undefined },\n os: { name: undefined, version: undefined },\n isBot: input === undefined ? false : isBot(input),\n };\n}\n\nexport function userAgent(context) {\n const headers = context?.headers;\n const ua = headers?.get ? headers.get(\"user-agent\") : undefined;\n return userAgentFromString(ua ?? undefined);\n}\n\nexport default {\n isBot,\n userAgent,\n userAgentFromString,\n};\n";
|
|
4
|
+
export declare function userAgentStubPlugin(): Plugin;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const USER_AGENT_STUB_ID = "\0cfni:user-agent-stub";
|
|
2
|
+
export const USER_AGENT_STUB_CODE = `
|
|
3
|
+
export function isBot(input) {
|
|
4
|
+
if (!input) return false;
|
|
5
|
+
return /Googlebot|Mediapartners-Google|AdsBot-Google|googleweblight|Storebot-Google|Google-PageRenderer|Google-InspectionTool|Bingbot|BingPreview|Slurp|DuckDuckBot|baiduspider|yandex|sogou|LinkedInBot|bitlybot|tumblr|vkShare|quora link preview|facebookexternalhit|facebookcatalog|Twitterbot|applebot|redditbot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|ia_archiver|GPTBot/i.test(
|
|
6
|
+
input
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function userAgentFromString(input) {
|
|
11
|
+
return {
|
|
12
|
+
ua: input ?? "",
|
|
13
|
+
browser: { name: undefined, version: undefined, major: undefined },
|
|
14
|
+
cpu: { architecture: undefined },
|
|
15
|
+
device: { model: undefined, type: undefined, vendor: undefined },
|
|
16
|
+
engine: { name: undefined, version: undefined },
|
|
17
|
+
os: { name: undefined, version: undefined },
|
|
18
|
+
isBot: input === undefined ? false : isBot(input),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function userAgent(context) {
|
|
23
|
+
const headers = context?.headers;
|
|
24
|
+
const ua = headers?.get ? headers.get("user-agent") : undefined;
|
|
25
|
+
return userAgentFromString(ua ?? undefined);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
isBot,
|
|
30
|
+
userAgent,
|
|
31
|
+
userAgentFromString,
|
|
32
|
+
};
|
|
33
|
+
`;
|
|
34
|
+
export function userAgentStubPlugin() {
|
|
35
|
+
return {
|
|
36
|
+
name: "cfni:user-agent-stub",
|
|
37
|
+
enforce: "pre",
|
|
38
|
+
resolveId(id) {
|
|
39
|
+
if (id === "next/dist/server/web/spec-extension/user-agent" || id.endsWith("/spec-extension/user-agent")) {
|
|
40
|
+
return USER_AGENT_STUB_ID;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
load(id) {
|
|
44
|
+
if (id === USER_AGENT_STUB_ID) {
|
|
45
|
+
return USER_AGENT_STUB_CODE;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
package/llms.txt
CHANGED
|
@@ -27,7 +27,7 @@ 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` — `buildIdAsset(fileName?)`: Vite plugin
|
|
30
|
+
- `./vite` — `cloudflareNextIntl(options?)` / `cloudflareNextIntlPlugin`, `buildIdAsset(fileName?)`, `localeFilePlugin(options?)`, `userAgentStubPlugin()`, `cfWorkersClientStubPlugin()`: 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, and emits client `BUILD_ID`).
|
|
31
31
|
- `./errorHandling` — error reporting & stale deploy recovery barrel: `reportError`, `withErrorHandling`, `installConsoleErrorOverride`, `installGlobalErrorOverride`, `stringifyUnknown`, `formatErrorMessage`, `defaultIgnoredConsoleErrors`, `createServerErrorAction`, `isStaleDeployError`, `defaultStaleDeployPatterns`, `setStaleDeployPatterns`, `getStaleDeployPatterns`, `clearClientCache`, `useStaleDeployRecovery`, `shouldRecoverFromStaleDeploy`, `isRecentBuild`.
|
|
32
32
|
- `./isStaleDeployError` — `isStaleDeployError(error, patterns?)`, `setStaleDeployPatterns(patterns)`, `getStaleDeployPatterns()`: detector returning `true` for version skew / chunk load / dynamic import / hydration errors (ChunkLoadError, 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`).
|
|
33
33
|
- `./clearClientCache` — `clearClientCache()`: async helper wiping `window.caches`, unregistering service workers, and clearing `sessionStorage` for recovering from stale deployments.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudflare-next-intl",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.53",
|
|
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",
|
|
@@ -219,8 +219,8 @@
|
|
|
219
219
|
"import": "./dist/src/db/schema.js"
|
|
220
220
|
},
|
|
221
221
|
"./vite": {
|
|
222
|
-
"types": "./dist/src/vite/
|
|
223
|
-
"import": "./dist/src/vite/
|
|
222
|
+
"types": "./dist/src/vite/index.d.ts",
|
|
223
|
+
"import": "./dist/src/vite/index.js"
|
|
224
224
|
}
|
|
225
225
|
},
|
|
226
226
|
"scripts": {
|