cloudflare-next-intl 0.6.16 → 0.6.18

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.
@@ -18,23 +18,24 @@ const ClarityScript = dynamic(() => import('./clarity_script'));
18
18
  * `false` — render manually instead if you set `autoWireAnalytics: false`.
19
19
  */
20
20
  export default function CookieConsentAnalytics({ config }) {
21
- const { consent } = useCookieConsent();
21
+ const { consent, requiresConsent } = useCookieConsent();
22
+ const granted = consent === true || !requiresConsent;
22
23
  useEffect(() => {
23
- if (consent === null)
24
+ if (consent === null && requiresConsent)
24
25
  return;
25
26
  const w = window;
26
27
  if (typeof w.gtag !== 'function')
27
28
  return;
28
- const state = consent ? 'granted' : 'denied';
29
+ const state = granted ? 'granted' : 'denied';
29
30
  w.gtag('consent', 'update', {
30
31
  ad_storage: state,
31
32
  ad_user_data: state,
32
33
  ad_personalization: state,
33
34
  analytics_storage: state,
34
35
  });
35
- }, [consent]);
36
+ }, [consent, requiresConsent, granted]);
36
37
  const hasGoogle = Boolean(config.googleAnalyticsId || config.googleAdsId || config.googleAdSenseId);
37
- return (_jsxs(_Fragment, { children: [hasGoogle && (_jsx("script", { id: "cookie-consent-google-consent-mode", dangerouslySetInnerHTML: { __html: googleConsentModeBootstrapScript(config) } })), consent === true && config.cloudflareBeaconToken && (_jsx("script", { defer: true, src: "https://static.cloudflareinsights.com/beacon.min.js", "data-cf-beacon": config.cloudflareBeaconToken })), consent === true && config.clarityProjectId && _jsx(ClarityScript, { projectId: config.clarityProjectId })] }));
38
+ return (_jsxs(_Fragment, { children: [hasGoogle && (_jsx("script", { id: "cookie-consent-google-consent-mode", dangerouslySetInnerHTML: { __html: googleConsentModeBootstrapScript(config) } })), granted && config.cloudflareBeaconToken && (_jsx("script", { defer: true, src: "https://static.cloudflareinsights.com/beacon.min.js", "data-cf-beacon": config.cloudflareBeaconToken })), granted && config.clarityProjectId && _jsx(ClarityScript, { projectId: config.clarityProjectId })] }));
38
39
  }
39
40
  /**
40
41
  * Denies storage by default and loads the configured Google tags; the
@@ -13,8 +13,8 @@ import { defaultCookieDialogClassNames } from './default_dialog_styles';
13
13
  * particular design system.
14
14
  */
15
15
  export default function CookieConsentDialog({ message, link, privacyPolicyLinkText, acceptText, declineText, hideDecline = false, id = 'cookie-consent-dialog', classNames, styles, render, }) {
16
- const { consent, isMounted, setConsent, privacyPolicyPath } = useCookieConsent();
17
- if (!isMounted || consent !== null)
16
+ const { consent, requiresConsent, isMounted, setConsent, privacyPolicyPath } = useCookieConsent();
17
+ if (!isMounted || !requiresConsent || consent !== null)
18
18
  return null;
19
19
  if (render)
20
20
  return _jsx(_Fragment, { children: render({ setConsent }) });
@@ -65,19 +65,18 @@ export default function CookieConsentProvider({ requiresConsent = true, children
65
65
  const [isMounted, setIsMounted] = useState(false);
66
66
  const pathname = usePathname();
67
67
  useEffect(() => {
68
- // `undefined` (no cookie at all) means a genuine first visit auto-
69
- // accept applies. `'null'` (explicitly stored by `setConsent(null)`,
70
- // e.g. a "cookie settings" button) means the visitor already decided
71
- // once and is being asked to re-decide, so it must NOT auto-accept
72
- // again otherwise it'd immediately flip back to `true` whenever
73
- // `requiresConsent` is `false` (e.g. always in dev, see
74
- // `resolveRequiresConsent`), reopening then instantly re-closing the
75
- // dialog on the next navigation/refresh.
68
+ // `consent` reflects only what the visitor actually decidednever
69
+ // auto-set to `true` just because `requiresConsent` is `false`.
70
+ // Whether the banner shows / analytics unlock for a not-required
71
+ // visitor is `requiresConsent`'s job (see `CookieConsentDialog`/
72
+ // `CookieConsentAnalytics`), not something baked into `consent`
73
+ // itself; otherwise there's no way to tell "not required" apart
74
+ // from "explicitly accepted" (e.g. the settings button in the nav
75
+ // bar, which only renders once consent has been decided).
76
76
  const rawConsent = getCookie(consentCookieName);
77
77
  const storedConsent = parseConsent(rawConsent);
78
78
  const isFirstVisit = rawConsent === null;
79
- const autoAccepted = isFirstVisit && !requiresConsent;
80
- setConsentState(autoAccepted ? true : storedConsent);
79
+ setConsentState(storedConsent);
81
80
  setIsMounted(true);
82
81
  if (isFirstVisit || !policyDate)
83
82
  return;
@@ -121,6 +120,6 @@ export default function CookieConsentProvider({ requiresConsent = true, children
121
120
  acknowledgePrivacyPolicyUpdate();
122
121
  }
123
122
  }, [pathname, privacyPolicyUpdated, privacyPolicyPath, acknowledgePrivacyPolicyUpdate]);
124
- const contextValue = useMemo(() => ({ consent, privacyPolicyUpdated, isMounted, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath }), [consent, privacyPolicyUpdated, isMounted, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath]);
123
+ const contextValue = useMemo(() => ({ consent, requiresConsent, privacyPolicyUpdated, isMounted, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath }), [consent, requiresConsent, privacyPolicyUpdated, isMounted, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath]);
125
124
  return (_jsx(CookieConsentContext.Provider, { value: contextValue, children: children }));
126
125
  }
@@ -4,6 +4,15 @@ export type ConsentValue = boolean | null;
4
4
  export interface CookieConsentContextType {
5
5
  /** Current consent value; `null` until the visitor decides. */
6
6
  consent: ConsentValue;
7
+ /**
8
+ * Resolved server-side from `cookieConsent.getCountryCode`/
9
+ * `gdprCountries` (see `resolveRequiresConsent`). `false` means the
10
+ * visitor's country doesn't require consent at all — the default
11
+ * `CookieConsentDialog` stays hidden and `CookieConsentAnalytics`
12
+ * unlocks immediately, even while `consent` itself is still `null`
13
+ * (never decided, since there was nothing to decide).
14
+ */
15
+ requiresConsent: boolean;
7
16
  /**
8
17
  * `false` until the client has read the stored consent/date cookies once
9
18
  * (always `false` during SSR and the first client render, to avoid a
@@ -17,11 +17,12 @@ import type { TranslationObject } from "../../types/types";
17
17
  * to serve from static rendering / ISR — i.e. the caller already knows
18
18
  * the current route never needs a server-resolved auth user (a public
19
19
  * page: marketing, privacy policy, docs, etc). Concretely, setting this
20
- * to `true` skips the internal `resolveAuthUserAndRedirect()` call.
20
+ * to `true` (the default) skips the internal `resolveAuthUserAndRedirect()`
21
+ * call. Pass `staticSafe: false` to opt back into resolving it.
21
22
  *
22
- * ── Why this call is normally made, and why skipping it is safe ──
23
- * When `firebaseAuth` is configured, `IntlProvider` by default calls
24
- * `resolveAuthUserAndRedirect()`, which:
23
+ * ── Why this call exists, and why skipping it by default is safe ──
24
+ * When `firebaseAuth` is configured and `staticSafe: false` is passed,
25
+ * `IntlProvider` calls `resolveAuthUserAndRedirect()`, which:
25
26
  * 1. Reads the session cookie via `cookies()` and verifies it against
26
27
  * Firebase (server-side, authoritative check for "who is this?").
27
28
  * 2. Reads the current pathname via `headers()` (`x-pathname`, set by
@@ -55,22 +56,21 @@ import type { TranslationObject } from "../../types/types";
55
56
  * render before the real user data appears — never wrong/protected
56
57
  * content, since middleware already gated that; just a delayed value.
57
58
  *
58
- * ── When to use it ──
59
- * Set `staticSafe: true` only on `IntlProvider` calls that wrap routes
60
- * you know are always public and don't render auth-dependent UI above
61
- * the fold (or can tolerate that UI appearing a moment late). Leave the
62
- * default (`false`) for any `IntlProvider` call that also wraps
63
- * protected routes or routes where the auth-state flash would be
64
- * visually jarring (dashboards, account pages, anything showing
65
- * `initialAuthUser`-derived content immediately). If you need
59
+ * ── When to opt out (staticSafe: false) ──
60
+ * Pass `staticSafe: false` on `IntlProvider` calls that wrap protected
61
+ * routes or routes where the auth-state flash would be visually jarring
62
+ * (dashboards, account pages, anything showing `initialAuthUser`-derived
63
+ * content immediately). Leave the default (`true`) for routes you know
64
+ * are always public and don't render auth-dependent UI above the fold
65
+ * (or can tolerate that UI appearing a moment late). If you need
66
66
  * different behavior for public vs protected routes within the SAME
67
67
  * app, render `IntlProvider` twice — once per layout/route-group, each
68
68
  * with its own `staticSafe` value — rather than picking one value for
69
69
  * the whole app. If `firebaseAuth.middlewareEnabled` is explicitly
70
- * `false` (middleware auth disabled), do NOT set `staticSafe: true` —
70
+ * `false` (middleware auth disabled), always pass `staticSafe: false` —
71
71
  * this component becomes the ONLY place performing the auth redirect,
72
- * so skipping it there really does remove the security check, not just
73
- * the flash.
72
+ * so leaving the `true` default there really does remove the security
73
+ * check, not just the flash.
74
74
  *
75
75
  * @example
76
76
  * ```tsx
@@ -27,11 +27,12 @@ let authUserServerProviderModule;
27
27
  * to serve from static rendering / ISR — i.e. the caller already knows
28
28
  * the current route never needs a server-resolved auth user (a public
29
29
  * page: marketing, privacy policy, docs, etc). Concretely, setting this
30
- * to `true` skips the internal `resolveAuthUserAndRedirect()` call.
30
+ * to `true` (the default) skips the internal `resolveAuthUserAndRedirect()`
31
+ * call. Pass `staticSafe: false` to opt back into resolving it.
31
32
  *
32
- * ── Why this call is normally made, and why skipping it is safe ──
33
- * When `firebaseAuth` is configured, `IntlProvider` by default calls
34
- * `resolveAuthUserAndRedirect()`, which:
33
+ * ── Why this call exists, and why skipping it by default is safe ──
34
+ * When `firebaseAuth` is configured and `staticSafe: false` is passed,
35
+ * `IntlProvider` calls `resolveAuthUserAndRedirect()`, which:
35
36
  * 1. Reads the session cookie via `cookies()` and verifies it against
36
37
  * Firebase (server-side, authoritative check for "who is this?").
37
38
  * 2. Reads the current pathname via `headers()` (`x-pathname`, set by
@@ -65,22 +66,21 @@ let authUserServerProviderModule;
65
66
  * render before the real user data appears — never wrong/protected
66
67
  * content, since middleware already gated that; just a delayed value.
67
68
  *
68
- * ── When to use it ──
69
- * Set `staticSafe: true` only on `IntlProvider` calls that wrap routes
70
- * you know are always public and don't render auth-dependent UI above
71
- * the fold (or can tolerate that UI appearing a moment late). Leave the
72
- * default (`false`) for any `IntlProvider` call that also wraps
73
- * protected routes or routes where the auth-state flash would be
74
- * visually jarring (dashboards, account pages, anything showing
75
- * `initialAuthUser`-derived content immediately). If you need
69
+ * ── When to opt out (staticSafe: false) ──
70
+ * Pass `staticSafe: false` on `IntlProvider` calls that wrap protected
71
+ * routes or routes where the auth-state flash would be visually jarring
72
+ * (dashboards, account pages, anything showing `initialAuthUser`-derived
73
+ * content immediately). Leave the default (`true`) for routes you know
74
+ * are always public and don't render auth-dependent UI above the fold
75
+ * (or can tolerate that UI appearing a moment late). If you need
76
76
  * different behavior for public vs protected routes within the SAME
77
77
  * app, render `IntlProvider` twice — once per layout/route-group, each
78
78
  * with its own `staticSafe` value — rather than picking one value for
79
79
  * the whole app. If `firebaseAuth.middlewareEnabled` is explicitly
80
- * `false` (middleware auth disabled), do NOT set `staticSafe: true` —
80
+ * `false` (middleware auth disabled), always pass `staticSafe: false` —
81
81
  * this component becomes the ONLY place performing the auth redirect,
82
- * so skipping it there really does remove the security check, not just
83
- * the flash.
82
+ * so leaving the `true` default there really does remove the security
83
+ * check, not just the flash.
84
84
  *
85
85
  * @example
86
86
  * ```tsx
@@ -96,7 +96,7 @@ let authUserServerProviderModule;
96
96
  * }
97
97
  * ```
98
98
  */
99
- export default async function LocationzationProvider({ language, messages, staticSafe = false, children }) {
99
+ export default async function LocationzationProvider({ language, messages, staticSafe = true, children }) {
100
100
  if (!localesSet.has(language)) {
101
101
  const { notFound } = await import("next/navigation");
102
102
  notFound();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.6.16",
3
+ "version": "0.6.18",
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",