cloudflare-next-intl 0.5.0 → 0.5.2

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.
@@ -2,20 +2,29 @@
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 { getLocaleCache } from '../../../general/cache_variables';
6
+ import { defaultCookieConsentText } from './default_dialog_text';
7
+ import { defaultCookieDialogClassNames } from './default_dialog_styles';
5
8
  /**
6
9
  * Cookie-consent banner. Renders `null` once `consent` is already decided.
7
10
  * Every visual aspect is overridable via `classNames`/`styles` (per-slot) or
8
11
  * `render` (full custom markup) — none of it is hardcoded to Tailwind or any
9
12
  * particular design system.
10
13
  */
11
- export default function CookieConsentDialog({ message = 'We use cookies to improve your experience.', link, privacyPolicyLinkText = 'Privacy Policy', acceptText = 'Accept', declineText = 'Necessary only', hideDecline = false, id = 'cookie-consent-dialog', classNames, styles, render, }) {
12
- const { consent, setConsent, privacyPolicyPath } = useCookieConsent();
13
- if (consent !== null)
14
+ export default function CookieConsentDialog({ message, link, privacyPolicyLinkText, acceptText, declineText, hideDecline = false, id = 'cookie-consent-dialog', classNames, styles, render, }) {
15
+ const { consent, isMounted, setConsent, privacyPolicyPath } = useCookieConsent();
16
+ if (!isMounted || consent !== null)
14
17
  return null;
15
18
  if (render)
16
19
  return _jsx(_Fragment, { children: render({ setConsent }) });
20
+ const text = defaultCookieConsentText[getLocaleCache() ?? 'en'] ?? defaultCookieConsentText.en;
21
+ const resolvedMessage = message ?? text.message;
22
+ const resolvedPrivacyPolicyLinkText = privacyPolicyLinkText ?? text.privacyPolicyLinkText;
23
+ const resolvedAcceptText = acceptText ?? text.acceptText;
24
+ const resolvedDeclineText = declineText ?? text.declineText;
25
+ const resolvedClassNames = { ...defaultCookieDialogClassNames, ...classNames };
17
26
  const resolvedLink = link !== undefined
18
27
  ? link
19
- : _jsx(DefaultPrivacyPolicyLink, { privacyPolicyPath: privacyPolicyPath, text: privacyPolicyLinkText });
20
- return (_jsxs("div", { id: id, role: "dialog", "aria-modal": "false", "aria-labelledby": `${id}-title`, className: classNames?.root, style: styles?.root, children: [_jsxs("p", { id: `${id}-title`, className: classNames?.message, style: styles?.message, children: [message, resolvedLink ? _jsxs("span", { className: classNames?.link, style: styles?.link, children: [" ", resolvedLink] }) : null] }), _jsxs("div", { className: classNames?.actions, style: styles?.actions, children: [!hideDecline && (_jsx("button", { type: "button", onClick: () => setConsent(false), className: classNames?.declineButton, style: styles?.declineButton, children: declineText })), _jsx("button", { type: "button", onClick: () => setConsent(true), className: classNames?.acceptButton, style: styles?.acceptButton, children: acceptText })] })] }));
28
+ : _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 })] })] }));
21
30
  }
@@ -0,0 +1,2 @@
1
+ import type { CookieDialogClassNames } from '../../types';
2
+ export declare const defaultCookieDialogClassNames: CookieDialogClassNames;
@@ -0,0 +1,9 @@
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',
3
+ message: 'text-sm sm:text-base text-gray-600 dark:text-gray-300',
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
+ actions: 'flex flex-row gap-2',
6
+ acceptButton: 'cursor-pointer bg-blue-600 text-white rounded-full px-4 py-2 hover:bg-blue-700 active:bg-blue-800 duration-200 dark:bg-blue-800 dark:hover:bg-blue-700 dark:active:bg-blue-900',
7
+ declineButton: 'cursor-pointer bg-blue-200 text-gray-800 rounded-full px-4 py-2 hover:bg-blue-300 active:bg-blue-400 duration-200 dark:bg-cyan-900 dark:text-white dark:hover:bg-cyan-800 dark:active:bg-cyan-950',
8
+ closeButton: 'cursor-pointer bg-blue-200 rounded-full p-2 hover:bg-blue-300 active:bg-blue-400 duration-200 dark:bg-blue-800 dark:text-white dark:hover:bg-blue-700 dark:active:bg-blue-900',
9
+ };
@@ -0,0 +1,13 @@
1
+ export interface CookieConsentDefaultText {
2
+ message: string;
3
+ privacyPolicyLinkText: string;
4
+ acceptText: string;
5
+ declineText: string;
6
+ }
7
+ export interface PrivacyPolicyUpdateDefaultText {
8
+ message: string;
9
+ privacyPolicyLinkText: string;
10
+ closeText: string;
11
+ }
12
+ export declare const defaultCookieConsentText: Record<string, CookieConsentDefaultText>;
13
+ export declare const defaultPrivacyPolicyUpdateText: Record<string, PrivacyPolicyUpdateDefaultText>;
@@ -0,0 +1,26 @@
1
+ export const defaultCookieConsentText = {
2
+ en: {
3
+ message: 'We use cookies to improve your experience.',
4
+ privacyPolicyLinkText: 'Privacy Policy',
5
+ acceptText: 'Accept',
6
+ declineText: 'Necessary only',
7
+ },
8
+ uk: {
9
+ message: 'Ми використовуємо файли cookie, щоб покращити ваш досвід.',
10
+ privacyPolicyLinkText: 'Політика конфіденційності',
11
+ acceptText: 'Прийняти',
12
+ declineText: 'Тільки необхідні',
13
+ },
14
+ };
15
+ export const defaultPrivacyPolicyUpdateText = {
16
+ en: {
17
+ message: 'Our privacy policy has been updated.',
18
+ privacyPolicyLinkText: 'Learn more',
19
+ closeText: 'Got it',
20
+ },
21
+ uk: {
22
+ message: 'Нашу політику конфіденційності було оновлено.',
23
+ privacyPolicyLinkText: 'Дізнатися більше',
24
+ closeText: 'Зрозуміло',
25
+ },
26
+ };
@@ -2,20 +2,28 @@
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 { getLocaleCache } from '../../../general/cache_variables';
6
+ import { defaultPrivacyPolicyUpdateText } from './default_dialog_text';
7
+ import { defaultCookieDialogClassNames } from './default_dialog_styles';
5
8
  /**
6
9
  * "Privacy policy updated" banner. Auto-enabled only when
7
10
  * `cookieConsent.privacyPolicyDate` is set on the `RoutingConfig` — renders
8
11
  * `null` otherwise, or once acknowledged. Every visual aspect is overridable
9
12
  * via `classNames`/`styles` (per-slot) or `render` (full custom markup).
10
13
  */
11
- export default function PrivacyPolicyUpdateDialog({ message = 'Our privacy policy has been updated.', link, privacyPolicyLinkText = 'Learn more', closeText = 'Got it', id = 'privacy-policy-update-dialog', classNames, styles, render, }) {
14
+ export default function PrivacyPolicyUpdateDialog({ message, link, privacyPolicyLinkText, closeText, id = 'privacy-policy-update-dialog', classNames, styles, render, }) {
12
15
  const { privacyPolicyUpdated, acknowledgePrivacyPolicyUpdate, privacyPolicyPath } = useCookieConsent();
13
16
  if (!privacyPolicyUpdated)
14
17
  return null;
15
18
  if (render)
16
19
  return _jsx(_Fragment, { children: render({ acknowledge: acknowledgePrivacyPolicyUpdate }) });
20
+ const text = defaultPrivacyPolicyUpdateText[getLocaleCache() ?? 'en'] ?? defaultPrivacyPolicyUpdateText.en;
21
+ const resolvedMessage = message ?? text.message;
22
+ const resolvedPrivacyPolicyLinkText = privacyPolicyLinkText ?? text.privacyPolicyLinkText;
23
+ const resolvedCloseText = closeText ?? text.closeText;
24
+ const resolvedClassNames = { ...defaultCookieDialogClassNames, ...classNames };
17
25
  const resolvedLink = link !== undefined
18
26
  ? link
19
- : _jsx(DefaultPrivacyPolicyLink, { privacyPolicyPath: privacyPolicyPath, text: privacyPolicyLinkText });
20
- return (_jsxs("div", { id: id, role: "dialog", "aria-modal": "false", "aria-labelledby": `${id}-title`, className: classNames?.root, style: styles?.root, children: [_jsxs("p", { id: `${id}-title`, className: classNames?.message, style: styles?.message, children: [message, resolvedLink ? _jsxs("span", { className: classNames?.link, style: styles?.link, children: [" ", resolvedLink] }) : null] }), _jsx("button", { type: "button", onClick: acknowledgePrivacyPolicyUpdate, "aria-label": closeText, className: classNames?.closeButton, style: styles?.closeButton, children: closeText })] }));
27
+ : _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 })] }));
21
29
  }
@@ -58,14 +58,14 @@ export default function CookieConsentProvider({ requiresConsent = true, children
58
58
  }, []);
59
59
  const [consent, setConsentState] = useState(null);
60
60
  const [privacyPolicyUpdated, setPrivacyPolicyUpdated] = useState(false);
61
+ const [isMounted, setIsMounted] = useState(false);
61
62
  useEffect(() => {
62
63
  const storedConsent = parseConsent(getCookie(consentCookieName));
63
- if (storedConsent === null && !requiresConsent) {
64
- setConsentState(true);
65
- return;
66
- }
67
- setConsentState(storedConsent);
68
- if (storedConsent === null || !policyDate)
64
+ const autoAccepted = storedConsent === null && !requiresConsent;
65
+ setConsentState(autoAccepted ? true : storedConsent);
66
+ setIsMounted(true);
67
+ const isFirstVisit = storedConsent === null && !autoAccepted;
68
+ if (isFirstVisit || !policyDate)
69
69
  return;
70
70
  const storedDateRaw = getCookie(dateCookieName);
71
71
  if (!storedDateRaw) {
@@ -90,6 +90,6 @@ export default function CookieConsentProvider({ requiresConsent = true, children
90
90
  setPrivacyPolicyUpdated(false);
91
91
  // eslint-disable-next-line react-hooks/exhaustive-deps
92
92
  }, []);
93
- const contextValue = useMemo(() => ({ consent, privacyPolicyUpdated, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath }), [consent, privacyPolicyUpdated, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath]);
93
+ const contextValue = useMemo(() => ({ consent, privacyPolicyUpdated, isMounted, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath }), [consent, privacyPolicyUpdated, isMounted, setConsent, acknowledgePrivacyPolicyUpdate, privacyPolicyPath]);
94
94
  return (_jsx(CookieConsentContext.Provider, { value: contextValue, children: children }));
95
95
  }
@@ -4,6 +4,14 @@ 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
+ * `false` until the client has read the stored consent/date cookies once
9
+ * (always `false` during SSR and the first client render, to avoid a
10
+ * hydration mismatch). The default dialog components render nothing
11
+ * until this is `true`, so a returning visitor's decided consent never
12
+ * flashes the banner before disappearing.
13
+ */
14
+ isMounted: boolean;
7
15
  /**
8
16
  * `true` once a privacy-policy update has been detected (stored consent
9
17
  * predates `cookieConsent.privacyPolicyDate`) and hasn't been
@@ -69,7 +69,7 @@ export default async function LocationzationProvider({ language, messages, child
69
69
  // (banner shown) same as an unresolved country would.
70
70
  requiresConsent = !isDevEnvironment
71
71
  ? await resolveRequiresConsent(config.cookieConsent.getCountryCode, config.cookieConsent.getCloudflareContext, config.cookieConsent.gdprCountries)
72
- : true;
72
+ : false;
73
73
  const analyticsAllowedInEnv = config.cookieConsent.enableAnalyticsInDevMode === true || !isDevEnvironment;
74
74
  if (config.cookieConsent.autoWireAnalytics !== false && analyticsAllowedInEnv) {
75
75
  analyticsSecrets = config.cookieConsent.getSecrets
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
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",
File without changes
@@ -1,39 +0,0 @@
1
- "use strict";
2
- // NOTE: currently disabled — this file exports nothing at runtime, even
3
- // though "./getLayoutStates" is listed in package.json's exports map.
4
- // Do not rely on `cloudflare-next-intl/getLayoutStates` until this is
5
- // re-enabled; use `getLocale()` + read the theme cookie manually instead.
6
- // "use server";
7
- // import { cookies } from "next/headers";
8
- // import { localeCookieName, isDarkCookieKey } from "../config/cookie_key";
9
- // import config from "../config/intl_config";
10
- // interface HtmlParamProps {
11
- // className?: string;
12
- // suppressHydrationWarning?: boolean;
13
- // lang: string;
14
- // }
15
- // export default async function getLayoutStates(): Promise<{
16
- // isDark: boolean | null;
17
- // locale: string;
18
- // htmlParam?: HtmlParamProps;
19
- // }> {
20
- // const cookie = await cookies();
21
- // const isDarkMode = cookie.get(isDarkCookieKey)?.value;
22
- // const isDark = getCookieBooleanValue(isDarkMode);
23
- // const cookieLocale = (cookie.get(localeCookieName)?.value as string) ?? config.defaultLocale;
24
- // const htmlParam: HtmlParamProps = { suppressHydrationWarning: !isDarkMode, lang: cookieLocale };
25
- // if (isDark === true) {
26
- // htmlParam.className = 'dark';
27
- // }
28
- // return { isDark, locale: cookieLocale, htmlParam };
29
- // }
30
- // function getCookieBooleanValue(cookieValue: string | undefined): boolean | null {
31
- // switch (cookieValue) {
32
- // case "true":
33
- // return true;
34
- // case "false":
35
- // return false;
36
- // default:
37
- // return null;
38
- // }
39
- // }