cloudflare-next-intl 0.5.2 → 0.5.4

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 CHANGED
@@ -188,8 +188,10 @@ import ThemeSwitcher from "cloudflare-next-intl/ThemeSwitcher";
188
188
  ### Cookie consent
189
189
 
190
190
  Set `cookieConsent` on your `RoutingConfig` to enable — `IntlProvider` then
191
- auto-wires `CookieConsentProvider` (and `CookieConsentAnalytics`, if
192
- `cookieConsent.secrets`/`getSecrets` is set) with no manual nesting needed.
191
+ auto-wires `CookieConsentProvider`, `CookieConsentAnalytics` (if
192
+ `cookieConsent.secrets`/`getSecrets` is set), and both `CookieConsentDialog`/
193
+ `PrivacyPolicyUpdateDialog` (with their built-in default styling and
194
+ English/Ukrainian copy) with no manual nesting needed.
193
195
 
194
196
  ```typescript
195
197
  // intl-config.ts
@@ -210,10 +212,17 @@ export default setIntlConfig({
210
212
  getCloudflareContext,
211
213
  // gdprCountries: [...], // defaults to EU/EEA + UK + Switzerland
212
214
  // enableAnalyticsInDevMode: true, // analytics stay off in dev otherwise
215
+ // autoWireDialogs: false, // opt out and render the dialogs yourself
216
+ // dialogProps: { acceptText: "Accept" }, // forwarded to CookieConsentDialog
217
+ // updateDialogProps: { closeText: "Got it" }, // forwarded to PrivacyPolicyUpdateDialog
213
218
  },
214
219
  });
215
220
  ```
216
221
 
222
+ `CookieConsentDialog`/`PrivacyPolicyUpdateDialog` render automatically — no
223
+ JSX needed in your layout. Set `autoWireDialogs: false` to render them
224
+ yourself instead (e.g. for full control over placement):
225
+
217
226
  ```tsx
218
227
  import { CookieConsentDialog, PrivacyPolicyUpdateDialog, useCookieConsent } from "cloudflare-next-intl/cookieConsent";
219
228
 
@@ -1,12 +1,14 @@
1
1
  import type { TranslationObject } from "../../types/types";
2
2
  import type { SerializedAuthUser } from "../../firebase_auth/types";
3
3
  import type { CookieConsentAnalyticsSecrets } from "../../types/types";
4
+ import type { CookieConsentDialogProps } from "../../cookie_consent/client/components/cookie_consent_dialog";
5
+ import type { PrivacyPolicyUpdateDialogProps } from "../../cookie_consent/client/components/privacy_policy_update_dialog";
4
6
  interface LocaleContextType {
5
7
  language: string;
6
8
  messages: TranslationObject;
7
9
  }
8
10
  export declare const LocaleContext: import("react").Context<LocaleContextType | undefined>;
9
- export default function LocationzationClientProvider({ language, messages, initialAuthUser, skipAuthProvider, analyticsSecrets, requiresConsent, children }: {
11
+ export default function LocationzationClientProvider({ language, messages, initialAuthUser, skipAuthProvider, analyticsSecrets, requiresConsent, autoWireDialogs, dialogProps, updateDialogProps, children }: {
10
12
  language: string;
11
13
  messages: TranslationObject;
12
14
  initialAuthUser?: SerializedAuthUser | null;
@@ -21,6 +23,12 @@ export default function LocationzationClientProvider({ language, messages, initi
21
23
  * for a first-time visitor instead of `null`.
22
24
  */
23
25
  requiresConsent?: boolean;
26
+ /** From `cookieConsent.autoWireDialogs` — renders `CookieConsentDialog`/`PrivacyPolicyUpdateDialog` automatically when `true` (default). */
27
+ autoWireDialogs?: boolean;
28
+ /** From `cookieConsent.dialogProps` — forwarded as-is to the auto-wired `CookieConsentDialog`. */
29
+ dialogProps?: CookieConsentDialogProps;
30
+ /** From `cookieConsent.updateDialogProps` — forwarded as-is to the auto-wired `PrivacyPolicyUpdateDialog`. */
31
+ updateDialogProps?: PrivacyPolicyUpdateDialogProps;
24
32
  children: React.ReactNode;
25
33
  }): Component;
26
34
  export {};
@@ -15,7 +15,9 @@ export const LocaleContext = createContext(undefined);
15
15
  const AuthUserProvider = dynamic(() => import("../../firebase_auth/client/auth_user_provider"));
16
16
  const CookieConsentProvider = dynamic(() => import("../../cookie_consent/client/cookie_consent_provider"));
17
17
  const CookieConsentAnalytics = dynamic(() => import("../../cookie_consent/client/components/cookie_consent_analytics"));
18
- export default function LocationzationClientProvider({ language, messages, initialAuthUser = null, skipAuthProvider = false, analyticsSecrets, requiresConsent = true, children }) {
18
+ const CookieConsentDialog = dynamic(() => import("../../cookie_consent/client/components/cookie_consent_dialog"));
19
+ const PrivacyPolicyUpdateDialog = dynamic(() => import("../../cookie_consent/client/components/privacy_policy_update_dialog"));
20
+ export default function LocationzationClientProvider({ language, messages, initialAuthUser = null, skipAuthProvider = false, analyticsSecrets, requiresConsent = true, autoWireDialogs = true, dialogProps, updateDialogProps, children }) {
19
21
  setLocaleCache(language);
20
22
  setMessageForLocaleCache(language, messages);
21
23
  // `LocaleContext.Provider` stays the outermost element here — the
@@ -28,7 +30,7 @@ export default function LocationzationClientProvider({ language, messages, initi
28
30
  providedChildren = _jsx(AuthUserProvider, { initialUser: initialAuthUser, children: children });
29
31
  }
30
32
  if (config.cookieConsent) {
31
- providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsSecrets && _jsx(CookieConsentAnalytics, { secrets: analyticsSecrets })] });
33
+ providedChildren = _jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, children: [providedChildren, analyticsSecrets && _jsx(CookieConsentAnalytics, { secrets: analyticsSecrets }), autoWireDialogs && _jsx(CookieConsentDialog, { ...dialogProps }), autoWireDialogs && _jsx(PrivacyPolicyUpdateDialog, { ...updateDialogProps })] });
32
34
  }
33
35
  const contextValue = useMemo(() => ({ language, messages }), [language, messages]);
34
36
  return _jsx(LocaleContext.Provider, { value: contextValue, children: providedChildren });
@@ -2,6 +2,7 @@
2
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import useCookieConsent from '../use_cookie_consent';
4
4
  import DefaultPrivacyPolicyLink from './default_privacy_policy_link';
5
+ import DialogPortal from './dialog_portal';
5
6
  import { getLocaleCache } from '../../../general/cache_variables';
6
7
  import { defaultCookieConsentText } from './default_dialog_text';
7
8
  import { defaultCookieDialogClassNames } from './default_dialog_styles';
@@ -26,5 +27,5 @@ export default function CookieConsentDialog({ message, link, privacyPolicyLinkTe
26
27
  const resolvedLink = link !== undefined
27
28
  ? link
28
29
  : _jsx(DefaultPrivacyPolicyLink, { privacyPolicyPath: privacyPolicyPath, text: resolvedPrivacyPolicyLinkText, className: resolvedClassNames.link });
29
- return (_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 })] })] }));
30
+ 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 })] })] }) }));
30
31
  }
@@ -1,5 +1,5 @@
1
1
  export const defaultCookieDialogClassNames = {
2
- root: 'fixed bottom-0 w-full bg-cyan-50 dark:bg-cyan-950 text-gray-800 dark:text-gray-200 z-20 flex flex-col sm:flex-row sm:justify-between items-center gap-3 sticky border-t border-t-gray-600 dark:border-t-gray-400 rounded-t-2xl px-4 py-3',
2
+ root: 'fixed bottom-0 w-full bg-cyan-50 dark:bg-cyan-950 text-gray-800 dark:text-gray-200 z-[2147483647] flex flex-col sm:flex-row sm:justify-between items-center gap-3 sticky border-t border-t-gray-600 dark:border-t-gray-400 rounded-t-2xl px-4 py-3',
3
3
  message: 'text-sm sm:text-base text-gray-600 dark:text-gray-300',
4
4
  link: 'underline font-bold duration-200 text-gray-600 hover:text-cyan-800 dark:text-gray-200 dark:hover:text-gray-100 active:text-cyan-700',
5
5
  actions: 'flex flex-row gap-2',
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Renders `children` into `document.body` via a portal, so the default
3
+ * dialogs always stack on top of the host app regardless of its own
4
+ * stacking contexts (a `z-index` alone can't escape an ancestor with
5
+ * `transform`/`filter`/`opacity`/`isolation`, all common in app shells).
6
+ * Only called client-side (both dialogs already gate on `isMounted`), so
7
+ * `document` is always defined here.
8
+ */
9
+ export default function DialogPortal({ children }: {
10
+ children: React.ReactNode;
11
+ }): React.ReactPortal;
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+ import { createPortal } from 'react-dom';
3
+ /**
4
+ * Renders `children` into `document.body` via a portal, so the default
5
+ * dialogs always stack on top of the host app regardless of its own
6
+ * stacking contexts (a `z-index` alone can't escape an ancestor with
7
+ * `transform`/`filter`/`opacity`/`isolation`, all common in app shells).
8
+ * Only called client-side (both dialogs already gate on `isMounted`), so
9
+ * `document` is always defined here.
10
+ */
11
+ export default function DialogPortal({ children }) {
12
+ return createPortal(children, document.body);
13
+ }
@@ -2,6 +2,7 @@
2
2
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import useCookieConsent from '../use_cookie_consent';
4
4
  import DefaultPrivacyPolicyLink from './default_privacy_policy_link';
5
+ import DialogPortal from './dialog_portal';
5
6
  import { getLocaleCache } from '../../../general/cache_variables';
6
7
  import { defaultPrivacyPolicyUpdateText } from './default_dialog_text';
7
8
  import { defaultCookieDialogClassNames } from './default_dialog_styles';
@@ -25,5 +26,5 @@ export default function PrivacyPolicyUpdateDialog({ message, link, privacyPolicy
25
26
  const resolvedLink = link !== undefined
26
27
  ? link
27
28
  : _jsx(DefaultPrivacyPolicyLink, { privacyPolicyPath: privacyPolicyPath, text: resolvedPrivacyPolicyLinkText, className: resolvedClassNames.link });
28
- return (_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 })] }));
29
+ 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 })] }) }));
29
30
  }
@@ -5,4 +5,6 @@ export { default as PrivacyPolicyUpdateDialog } from './client/components/privac
5
5
  export { default as CookieConsentAnalytics } from './client/components/cookie_consent_analytics';
6
6
  export { defaultGdprCountries } from './gdpr_countries';
7
7
  export type { CookieConsentContextType, ConsentValue, CookieDialogClassNames, CookieDialogStyles } from './types';
8
+ export type { CookieConsentDialogProps } from './client/components/cookie_consent_dialog';
9
+ export type { PrivacyPolicyUpdateDialogProps } from './client/components/privacy_policy_update_dialog';
8
10
  export type { CookieConsentRoutingConfig, CookieConsentAnalyticsSecrets, CookieConsentCloudflareContext, CookieConsentGetCloudflareContext, } from '../types/types';
@@ -77,5 +77,5 @@ export default async function LocationzationProvider({ language, messages, child
77
77
  : config.cookieConsent.secrets;
78
78
  }
79
79
  }
80
- return _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, initialAuthUser: initialAuthUser, skipAuthProvider: !autoWireClientProvider, analyticsSecrets: analyticsSecrets, requiresConsent: requiresConsent, children: children });
80
+ return _jsx(LocationzationClientProvider, { language: language, messages: messagesValue, initialAuthUser: initialAuthUser, skipAuthProvider: !autoWireClientProvider, analyticsSecrets: analyticsSecrets, requiresConsent: requiresConsent, autoWireDialogs: config.cookieConsent?.autoWireDialogs !== false, dialogProps: config.cookieConsent?.dialogProps, updateDialogProps: config.cookieConsent?.updateDialogProps, children: children });
81
81
  }
@@ -1,6 +1,8 @@
1
1
  import type { NextResponse } from 'next/server';
2
2
  import type { Languages } from 'next/dist/lib/metadata/types/alternative-urls-types';
3
3
  import type { Videos } from 'next/dist/lib/metadata/types/metadata-types';
4
+ import type { CookieConsentDialogProps } from '../cookie_consent/client/components/cookie_consent_dialog';
5
+ import type { PrivacyPolicyUpdateDialogProps } from '../cookie_consent/client/components/privacy_policy_update_dialog';
4
6
  /**
5
7
  * Custom middleware hook, run by `intlMiddleware` for your own logic
6
8
  * (e.g. auth, feature flags, A/B tests) — on top of the library's own
@@ -179,6 +181,26 @@ export interface CookieConsentRoutingConfig {
179
181
  * scripts locally.
180
182
  */
181
183
  enableAnalyticsInDevMode?: boolean;
184
+ /**
185
+ * Whether `IntlProvider` should automatically render
186
+ * `CookieConsentDialog` and `PrivacyPolicyUpdateDialog` (with their
187
+ * built-in default styling/EN+UK copy) at the end of your app tree.
188
+ * Defaults to `true` whenever `cookieConsent` is set. Set `false` to
189
+ * keep `cookieConsent` configured for `useCookieConsent()`/analytics
190
+ * only and render your own dialogs (or the exported components)
191
+ * yourself, wherever you like in the tree.
192
+ */
193
+ autoWireDialogs?: boolean;
194
+ /**
195
+ * Props forwarded as-is to the auto-wired `CookieConsentDialog`.
196
+ * Ignored when `autoWireDialogs` is `false`.
197
+ */
198
+ dialogProps?: CookieConsentDialogProps;
199
+ /**
200
+ * Props forwarded as-is to the auto-wired `PrivacyPolicyUpdateDialog`.
201
+ * Ignored when `autoWireDialogs` is `false`.
202
+ */
203
+ updateDialogProps?: PrivacyPolicyUpdateDialogProps;
182
204
  }
183
205
  /**
184
206
  * Minimal shape read from your `getCloudflareContext()` return value — only
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
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",