cloudflare-next-intl 0.8.52 → 0.8.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +48 -6
  2. package/dist/package.json +314 -0
  3. package/dist/src/config/middleware.js +21 -6
  4. package/dist/src/cookie_consent/client/components/cookie_consent_dialog.d.ts +6 -1
  5. package/dist/src/cookie_consent/client/components/cookie_consent_dialog.js +6 -3
  6. package/dist/src/cookie_consent/client/components/privacy_policy_update_dialog.d.ts +6 -1
  7. package/dist/src/cookie_consent/client/components/privacy_policy_update_dialog.js +6 -3
  8. package/dist/src/cookie_consent/client/cookie_consent_provider.js +3 -2
  9. package/dist/src/cookie_consent/gdpr_countries.d.ts +2 -2
  10. package/dist/src/cookie_consent/gdpr_countries.js +2 -2
  11. package/dist/src/cookie_consent/types.d.ts +5 -0
  12. package/dist/src/image_optimizer/blur_svg.d.ts +1 -0
  13. package/dist/src/image_optimizer/blur_svg.js +16 -0
  14. package/dist/src/image_optimizer/cache.d.ts +10 -0
  15. package/dist/src/image_optimizer/cache.js +34 -0
  16. package/dist/src/image_optimizer/index.d.ts +8 -0
  17. package/dist/src/image_optimizer/index.js +7 -0
  18. package/dist/src/image_optimizer/manifest.d.ts +3 -0
  19. package/dist/src/image_optimizer/manifest.js +25 -0
  20. package/dist/src/image_optimizer/next_image_shim.d.ts +9 -0
  21. package/dist/src/image_optimizer/next_image_shim.js +38 -0
  22. package/dist/src/image_optimizer/plugin.d.ts +8 -0
  23. package/dist/src/image_optimizer/plugin.js +64 -0
  24. package/dist/src/image_optimizer/process_image.d.ts +12 -0
  25. package/dist/src/image_optimizer/process_image.js +89 -0
  26. package/dist/src/image_optimizer/run.d.ts +3 -0
  27. package/dist/src/image_optimizer/run.js +67 -0
  28. package/dist/src/image_optimizer/test_helpers.d.ts +3 -0
  29. package/dist/src/image_optimizer/test_helpers.js +25 -0
  30. package/dist/src/image_optimizer/types.d.ts +88 -0
  31. package/dist/src/image_optimizer/types.js +82 -0
  32. package/dist/src/server/components/server_provider.js +1 -1
  33. package/dist/src/server/components/server_provider_static.js +1 -1
  34. package/dist/src/server/functions/geo.d.ts +2 -2
  35. package/dist/src/server/functions/geo.js +36 -12
  36. package/dist/src/types/types.d.ts +6 -0
  37. package/dist/src/vite/cf_workers_client_stub.d.ts +4 -0
  38. package/dist/src/vite/cf_workers_client_stub.js +23 -0
  39. package/dist/src/vite/index.d.ts +6 -0
  40. package/dist/src/vite/index.js +6 -0
  41. package/dist/src/vite/locale_file_plugin.d.ts +19 -0
  42. package/dist/src/vite/locale_file_plugin.js +90 -0
  43. package/dist/src/vite/plugin.d.ts +37 -0
  44. package/dist/src/vite/plugin.js +33 -0
  45. package/dist/src/vite/user_agent_stub.d.ts +4 -0
  46. package/dist/src/vite/user_agent_stub.js +49 -0
  47. package/llms.txt +2 -1
  48. package/package.json +13 -4
package/README.md CHANGED
@@ -239,22 +239,63 @@ export default setIntlConfig({
239
239
  });
240
240
  ```
241
241
 
242
- #### Vite Build ID Asset Plugin (`cloudflare-next-intl/vite`)
242
+ #### Vite Plugin for Vinext & Cloudflare Workers (`cloudflare-next-intl/vite`)
243
243
 
244
- When building client assets with Vite / Vinext for Cloudflare Workers, use `buildIdAsset` to emit the static `BUILD_ID` asset:
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 { buildIdAsset } from "cloudflare-next-intl/vite";
249
+ import { cloudflareNextIntl } from "cloudflare-next-intl/vite";
250
250
 
251
251
  export default defineConfig({
252
252
  plugins: [
253
- buildIdAsset(), // reads __VINEXT_SHARED_BUILD_ID or __VINEXT_BUILD_ID
253
+ cloudflareNextIntl(), // All plugins enabled by default
254
254
  ],
255
255
  });
256
256
  ```
257
257
 
258
+ ##### What `cloudflareNextIntl()` Does
259
+ 1. **Build-Time & Dev Image Optimizer (`imageOptimizer`)**: Automatically scans your image directories (`public/images`, `public/icons`), downscales oversized assets, produces modern `.avif` and `.webp` formats, generates 8px `.blur.webp` thumbnails with Next.js-matching SVG Gaussian blur placeholders, and provides transparent `<Image placeholder="blur" />` shimming via virtual modules.
260
+ 2. **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.
261
+ 3. **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).
262
+ 4. **Cloudflare Workers Client Stub (`cfWorkersClientStub`)**: Stubs `cloudflare:workers` in client builds so shared modules can be referenced without client bundling errors.
263
+ 5. **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`.
264
+
265
+ ##### Plugin Options
266
+ All features are enabled by default, and can be individually configured or toggled off:
267
+
268
+ ```typescript
269
+ import { defineConfig } from "vite";
270
+ import { cloudflareNextIntl } from "cloudflare-next-intl/vite";
271
+
272
+ export default defineConfig({
273
+ plugins: [
274
+ cloudflareNextIntl({
275
+ imageOptimizer: { // Image optimizer configuration (or `false` to disable)
276
+ maxWidth: 1920, // Downscale max width limit (default: 1920, or `false`)
277
+ formats: ["avif", "webp"], // Target sibling formats (default: ["avif", "webp"], or `false`)
278
+ quality: 80, // Compression quality (default: 80)
279
+ blur: { quality: 70, stdDeviation: 20 }, // Next.js blur placeholder options (or `false`)
280
+ overrides: { // Per-image overrides keyed by public src path
281
+ "/images/hero.png": { maxWidth: false, formats: ["webp"], blur: { quality: 80 } },
282
+ "/images/logo.png": { formats: false, blur: false },
283
+ },
284
+ },
285
+ messagesDir: "./messages", // Path to locale JSON files (default: './messages')
286
+ intlConfigPath: "./src/l18n/intl_config.ts", // Path to intl config (auto-detected if omitted)
287
+ buildIdAsset: true, // Emit BUILD_ID asset (or custom string filename, default: true)
288
+ localeFiles: true, // Enable @locale-file & glob bundling (default: true)
289
+ userAgentStub: true, // Enable regex-based user-agent stub (default: true)
290
+ cfWorkersClientStub: true, // Enable client cloudflare:workers stub (default: true)
291
+ }),
292
+ ],
293
+ });
294
+ ```
295
+
296
+ Individual standalone plugins are also exported if you only need a specific feature:
297
+ `imageOptimizerPlugin` (or `imageOptimizer`), `buildIdAsset`, `localeFilePlugin`, `userAgentStubPlugin`, `cfWorkersClientStubPlugin`.
298
+
258
299
  ```tsx
259
300
  // Client Components ("use client")
260
301
  import { useLocale } from "cloudflare-next-intl/use";
@@ -405,6 +446,7 @@ export default setIntlConfig({
405
446
  },
406
447
  cookieConsent: {
407
448
  privacyPolicyDate: "2026-01-01",
449
+ // showPrivacyPolicy: false, // defaults to true; set false to hide privacy policy link in dialogs
408
450
  // privacyPolicyPath: "/privacy-policy", // default; used by the
409
451
  // dialogs' auto-rendered link. Set false to disable that link.
410
452
  // country-based gating is enabled by default (reads Cloudflare geo
@@ -414,8 +456,8 @@ export default setIntlConfig({
414
456
  // gdprCountries: [...], // defaults to EU/EEA + UK + Switzerland
415
457
  // enableAnalyticsInDevMode: true, // analytics stay off in dev otherwise
416
458
  // autoWireDialogs: false, // opt out and render the dialogs yourself
417
- // dialogProps: { acceptText: "Accept" }, // forwarded to CookieConsentDialog
418
- // updateDialogProps: { closeText: "Got it" }, // forwarded to PrivacyPolicyUpdateDialog
459
+ // dialogProps: { acceptText: "Accept", showPrivacyPolicy: true }, // forwarded to CookieConsentDialog
460
+ // updateDialogProps: { closeText: "Got it", showPrivacyPolicy: true }, // forwarded to PrivacyPolicyUpdateDialog
419
461
  },
420
462
  });
421
463
  ```
@@ -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
- : _jsx(DefaultPrivacyPolicyLink, { privacyPolicyPath: privacyPolicyPath, text: resolvedPrivacyPolicyLinkText, className: resolvedClassNames.link });
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
- : _jsx(DefaultPrivacyPolicyLink, { privacyPolicyPath: privacyPolicyPath, text: resolvedPrivacyPolicyLinkText, className: resolvedClassNames.link });
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, undefined, countryHeaderNames);
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 {
@@ -0,0 +1 @@
1
+ export declare function getImageBlurSvg(blurDataURL: string, blurWidth?: number, blurHeight?: number, objectFit?: string, stdDeviation?: number): string;
@@ -0,0 +1,16 @@
1
+ export function getImageBlurSvg(blurDataURL, blurWidth, blurHeight, objectFit, stdDeviation = 20) {
2
+ const std = stdDeviation;
3
+ const viewBox = blurWidth && blurHeight
4
+ ? `viewBox='0 0 ${blurWidth * 40} ${blurHeight * 40}'`
5
+ : "";
6
+ const preserveAspectRatio = viewBox
7
+ ? "none"
8
+ : objectFit === "contain"
9
+ ? "xMidYMid"
10
+ : objectFit === "cover"
11
+ ? "xMidYMid slice"
12
+ : "none";
13
+ const svg = `<svg xmlns='http://www.w3.org/2000/svg' ${viewBox}><filter id='b' color-interpolation-filters='sRGB'><feGaussianBlur stdDeviation='${std}'/><feColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/><feFlood x='0' y='0' width='100%' height='100%'/><feComposite operator='out' in='s'/><feComposite in2='SourceGraphic'/><feGaussianBlur stdDeviation='${std}'/></filter><image width='100%' height='100%' x='0' y='0' preserveAspectRatio='${preserveAspectRatio}' style='filter: url(#b);' href='${blurDataURL}'/></svg>`;
14
+ const base64 = Buffer.from(svg).toString("base64");
15
+ return `data:image/svg+xml;base64,${base64}`;
16
+ }