cloudflare-next-intl 0.13.0 → 0.13.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.
package/README.md CHANGED
@@ -599,6 +599,8 @@ export default setIntlConfig({
599
599
  // countryHeaderNames: ["x-cf-country", "cf-ipcountry"],
600
600
  // gdprCountries: [...], // defaults to EU/EEA + UK + Switzerland
601
601
  // enableAnalyticsInDevMode: true, // analytics stay off in dev otherwise
602
+ // googleTagId: "GT-XXXXXXXX", // gtag.js in IntlHelperScript; defaults to analytics.googleAdsId
603
+ // googleTag: false, // turn off the IntlHelperScript Google tag
602
604
  // autoWireDialogs: false, // opt out and render the dialogs yourself
603
605
  // dialogProps: { acceptText: "Accept", showPrivacyPolicy: true }, // forwarded to CookieConsentDialog
604
606
  // updateDialogProps: { closeText: "Got it", showPrivacyPolicy: true }, // forwarded to PrivacyPolicyUpdateDialog
@@ -31,7 +31,7 @@ export default function ClientExtensions({ initialAuthUser = null, skipAuthProvi
31
31
  providedChildren = (_jsxs(AuthUserProvider, { initialUser: initialAuthUser, children: [children, isPerf && (_jsx(Suspense, { fallback: null, children: _jsx(AutoFirebasePerformanceEvents, {}) }))] }));
32
32
  }
33
33
  if (isConsent) {
34
- providedChildren = (_jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, cookieConsentConfig: resolvedConsent, children: [providedChildren, analyticsConfig && (_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentAnalytics, { config: analyticsConfig }) })), analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId) && (_jsx(Suspense, { fallback: null, children: _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig ?? resolvedConsent?.autoAnalyticsEvents }) })), autoWireDialogs && (_jsxs(_Fragment, { children: [_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentDialog, { ...(dialogProps ?? resolvedConsent?.dialogProps) }) }), _jsx(Suspense, { fallback: null, children: _jsx(PrivacyPolicyUpdateDialog, { ...(updateDialogProps ?? resolvedConsent?.updateDialogProps) }) })] }))] }));
34
+ providedChildren = (_jsxs(CookieConsentProvider, { requiresConsent: requiresConsent, cookieConsentConfig: resolvedConsent, children: [providedChildren, analyticsConfig && (_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentAnalytics, { config: analyticsConfig }) })), analyticsConfig && (analyticsConfig.googleAnalyticsId || analyticsConfig.googleAdsId || (typeof analyticsConfig.googleTagId === 'string' && analyticsConfig.googleTagId)) && (_jsx(Suspense, { fallback: null, children: _jsx(AutoAnalyticsEvents, { config: autoAnalyticsEventsConfig ?? resolvedConsent?.autoAnalyticsEvents }) })), autoWireDialogs && (_jsxs(_Fragment, { children: [_jsx(Suspense, { fallback: null, children: _jsx(CookieConsentDialog, { ...(dialogProps ?? resolvedConsent?.dialogProps) }) }), _jsx(Suspense, { fallback: null, children: _jsx(PrivacyPolicyUpdateDialog, { ...(updateDialogProps ?? resolvedConsent?.updateDialogProps) }) })] }))] }));
35
35
  }
36
36
  return (_jsxs(_Fragment, { children: [_jsx(ClientThemeScript, {}), providedChildren] }));
37
37
  }
@@ -34,19 +34,30 @@ export default function CookieConsentAnalytics({ config }) {
34
34
  script.setAttribute('data-cf-beacon', config.cloudflareBeaconToken);
35
35
  document.head.appendChild(script);
36
36
  }, [granted, config.cloudflareBeaconToken]);
37
- const hasGoogle = Boolean(config.googleAnalyticsId || config.googleAdsId || config.googleAdSenseId);
37
+ const hasGoogle = Boolean(config.googleAnalyticsId ||
38
+ config.googleAdsId ||
39
+ config.googleAdSenseId ||
40
+ (typeof config.googleTagId === 'string' && config.googleTagId));
38
41
  return (_jsxs(_Fragment, { children: [hasGoogle && (_jsx("script", { id: "cookie-consent-google-consent-mode", dangerouslySetInnerHTML: { __html: googleConsentModeBootstrapScript(config) } })), hydrated && granted && config.clarityProjectId && (_jsx(Suspense, { fallback: null, children: _jsx(ClarityScript, { projectId: config.clarityProjectId }) }))] }));
39
42
  }
40
43
  export function googleConsentModeBootstrapScript(config) {
41
- const configCalls = [config.googleAnalyticsId, config.googleAdsId]
44
+ const configCalls = [
45
+ config.googleAnalyticsId,
46
+ config.googleAdsId,
47
+ typeof config.googleTagId === 'string' ? config.googleTagId : undefined,
48
+ ]
42
49
  .filter(Boolean)
50
+ .filter((id, index, arr) => arr.indexOf(id) === index)
43
51
  .map((id) => `gtag('config', '${id}');`)
44
52
  .join('\n');
45
- const gtagLoader = config.googleAnalyticsId || config.googleAdsId
53
+ const gtagTargetId = config.googleAnalyticsId ??
54
+ config.googleAdsId ??
55
+ (typeof config.googleTagId === 'string' ? config.googleTagId : undefined);
56
+ const gtagLoader = gtagTargetId
46
57
  ? `(function(){
47
58
  var s = document.createElement('script');
48
59
  s.async = true;
49
- s.src = 'https://www.googletagmanager.com/gtag/js?id=${config.googleAnalyticsId ?? config.googleAdsId}';
60
+ s.src = 'https://www.googletagmanager.com/gtag/js?id=${gtagTargetId}';
50
61
  document.head.appendChild(s);
51
62
  })();`
52
63
  : '';
@@ -17,6 +17,10 @@ function parseConsent(raw) {
17
17
  return null;
18
18
  }
19
19
  const defaultGdprCountriesSet = new Set(defaultGdprCountries);
20
+ const countryCookiePollMs = 200;
21
+ const countryCookieRevealMs = 2000;
22
+ const countryCookieMaxWaitMs = 15000;
23
+ const countryCookieRefreshParam = '__cfni_country';
20
24
  export default function CookieConsentProvider({ requiresConsent: requiresConsentProp = true, cookieConsentConfig, children, }) {
21
25
  const { consentCookieName, dateCookieName, maxAge, policyDate, privacyPolicyPath, showPrivacyPolicy, gdprCountries } = useMemo(() => {
22
26
  const cc = requireCookieConsentConfig(cookieConsentConfig ?? config.cookieConsent);
@@ -40,25 +44,48 @@ export default function CookieConsentProvider({ requiresConsent: requiresConsent
40
44
  const storedConsent = parseConsent(rawConsent);
41
45
  const isFirstVisit = rawConsent === null;
42
46
  setConsentState(storedConsent);
43
- if (requiresConsentProp) {
47
+ const gdprSet = gdprCountries ? new Set(gdprCountries) : defaultGdprCountriesSet;
48
+ const applyCountryCookie = () => {
44
49
  const countryCookie = getCookie(countryCookieKey);
45
- if (countryCookie) {
46
- const gdprSet = gdprCountries
47
- ? new Set(gdprCountries)
48
- : defaultGdprCountriesSet;
49
- setRequiresConsent(gdprSet.has(countryCookie));
50
- }
50
+ if (!countryCookie)
51
+ return false;
52
+ setRequiresConsent(gdprSet.has(countryCookie));
53
+ return true;
54
+ };
55
+ let pollId;
56
+ let revealId;
57
+ let stopId;
58
+ const stopPolling = () => {
59
+ clearInterval(pollId);
60
+ clearTimeout(revealId);
61
+ clearTimeout(stopId);
62
+ };
63
+ if (!requiresConsentProp || applyCountryCookie()) {
64
+ setIsMounted(true);
51
65
  }
52
- setIsMounted(true);
53
- if (isFirstVisit || !policyDate)
54
- return;
55
- const storedDateRaw = getCookie(dateCookieName);
56
- if (!storedDateRaw) {
57
- setCookie({ name: dateCookieName, value: policyDate.toISOString(), maxAge });
58
- return;
66
+ else {
67
+ fetch(`${window.location.pathname}?${countryCookieRefreshParam}=${Date.now()}`, { method: 'HEAD', cache: 'no-store', credentials: 'same-origin' })
68
+ .catch(() => undefined);
69
+ pollId = setInterval(() => {
70
+ if (!applyCountryCookie())
71
+ return;
72
+ stopPolling();
73
+ setIsMounted(true);
74
+ }, countryCookiePollMs);
75
+ revealId = setTimeout(() => setIsMounted(true), countryCookieRevealMs);
76
+ stopId = setTimeout(stopPolling, countryCookieMaxWaitMs);
77
+ }
78
+ if (!isFirstVisit && policyDate) {
79
+ const storedDateRaw = getCookie(dateCookieName);
80
+ if (!storedDateRaw) {
81
+ setCookie({ name: dateCookieName, value: policyDate.toISOString(), maxAge });
82
+ }
83
+ else {
84
+ const storedDate = new Date(storedDateRaw);
85
+ setPrivacyPolicyUpdated(!Number.isNaN(storedDate.getTime()) && storedDate < policyDate);
86
+ }
59
87
  }
60
- const storedDate = new Date(storedDateRaw);
61
- setPrivacyPolicyUpdated(!Number.isNaN(storedDate.getTime()) && storedDate < policyDate);
88
+ return stopPolling;
62
89
  }, [consentCookieName, dateCookieName, maxAge, policyDate, requiresConsentProp, gdprCountries]);
63
90
  const setConsent = useCallback((value) => {
64
91
  if (value === null) {
@@ -1,6 +1,9 @@
1
1
  import React from 'react';
2
2
  import { defaultReloadHtml } from 'cloudflare-next-intl-errors/staleDeployScripts';
3
3
  export { defaultReloadHtml };
4
- export default function HelperScript({ locale }: {
4
+ export interface HelperScriptProps {
5
5
  locale?: string;
6
- }): React.JSX.Element;
6
+ googleTag?: boolean;
7
+ googleTagId?: string | false;
8
+ }
9
+ export default function HelperScript({ locale, googleTag, googleTagId, }?: HelperScriptProps): React.JSX.Element;
@@ -1,12 +1,28 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import CoreHelperScript from 'cloudflare-next-intl-intl/internal/helperScript';
3
3
  import ThemeScript from 'cloudflare-next-intl-theme/ThemeScript';
4
4
  import StaleDeployScripts, { defaultReloadHtml } from 'cloudflare-next-intl-errors/staleDeployScripts';
5
5
  import config from '../../config/intl_config.js';
6
6
  export { defaultReloadHtml };
7
- export default function HelperScript({ locale }) {
7
+ export default function HelperScript({ locale, googleTag, googleTagId, } = {}) {
8
8
  const analytics = config.cookieConsent?.analytics;
9
- const hasGoogle = !!(analytics?.googleAnalyticsId || analytics?.googleAdsId || analytics?.googleAdSenseId);
9
+ const cookieConsent = config.cookieConsent;
10
+ const isGoogleTagExplicitlyOff = googleTag === false ||
11
+ googleTagId === false ||
12
+ analytics?.googleTag === false ||
13
+ analytics?.googleTagId === false ||
14
+ cookieConsent?.googleTag === false ||
15
+ cookieConsent?.googleTagId === false;
16
+ const rawTagId = (typeof googleTagId === 'string' ? googleTagId : undefined) ||
17
+ (typeof analytics?.googleTagId === 'string' ? analytics.googleTagId : undefined) ||
18
+ (typeof cookieConsent?.googleTagId === 'string' ? cookieConsent.googleTagId : undefined) ||
19
+ analytics?.googleAdsId;
20
+ const resolvedGoogleTagId = !isGoogleTagExplicitlyOff ? rawTagId : undefined;
21
+ const shouldRenderGoogleTag = Boolean(resolvedGoogleTagId && !isGoogleTagExplicitlyOff);
22
+ const hasGoogle = !!(analytics?.googleAnalyticsId ||
23
+ analytics?.googleAdsId ||
24
+ analytics?.googleAdSenseId ||
25
+ shouldRenderGoogleTag);
10
26
  const hasClarity = !!analytics?.clarityProjectId;
11
27
  const hasFirebase = !!config.firebaseAuth;
12
28
  const hasTurnstile = !!config.firebaseAuth?.appCheck?.turnstileSiteKey;
@@ -22,5 +38,7 @@ export default function HelperScript({ locale }) {
22
38
  const preconnectOrigins = [];
23
39
  if (hasTurnstile)
24
40
  preconnectOrigins.push('https://challenges.cloudflare.com');
25
- return (_jsxs(CoreHelperScript, { locale: locale, prefetchOrigins: prefetchOrigins.length > 0 ? prefetchOrigins : undefined, preconnectOrigins: preconnectOrigins.length > 0 ? preconnectOrigins : undefined, children: [_jsx(ThemeScript, {}), _jsx(StaleDeployScripts, { reloadHtml: config.errorHandling?.staleDeployReloadHtml })] }));
41
+ return (_jsxs(CoreHelperScript, { locale: locale, prefetchOrigins: prefetchOrigins.length > 0 ? prefetchOrigins : undefined, preconnectOrigins: preconnectOrigins.length > 0 ? preconnectOrigins : undefined, children: [_jsx(ThemeScript, {}), _jsx(StaleDeployScripts, { reloadHtml: config.errorHandling?.staleDeployReloadHtml }), shouldRenderGoogleTag && (_jsxs(_Fragment, { children: [_jsx("script", { async: true, src: `https://www.googletagmanager.com/gtag/js?id=${resolvedGoogleTagId}` }), _jsx("script", { id: "google-tag-init", dangerouslySetInnerHTML: {
42
+ __html: `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', '${resolvedGoogleTagId}');`,
43
+ } })] }))] }));
26
44
  }
@@ -20,6 +20,8 @@ export interface CookieConsentRoutingConfig {
20
20
  privacyPolicyDateCookieName?: string;
21
21
  cookieMaxAge?: number;
22
22
  autoWireAnalytics?: boolean;
23
+ googleTag?: boolean;
24
+ googleTagId?: string | false;
23
25
  analytics?: CookieConsentAnalyticsConfig;
24
26
  getAnalytics?: () => CookieConsentAnalyticsConfig | Promise<CookieConsentAnalyticsConfig>;
25
27
  autoAnalyticsEvents?: AutoAnalyticsEventsConfig;
@@ -55,6 +57,8 @@ export interface CookieConsentAnalyticsConfig {
55
57
  cloudflareBeaconToken?: string;
56
58
  googleAnalyticsId?: string;
57
59
  googleAdsId?: string;
60
+ googleTagId?: string | false;
61
+ googleTag?: boolean;
58
62
  googleAdSenseId?: string;
59
63
  clarityProjectId?: string;
60
64
  }
@@ -8,6 +8,7 @@ export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from ".
8
8
  export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
9
9
  export { bufferStubPlugin, BUFFER_STUB_ID } from "./buffer_stub.js";
10
10
  export { tailwindSourcesPlugin } from "./tailwind_sources_plugin.js";
11
+ export { staticNotFoundNoStorePlugin, appendStaticNoStoreRule, STATIC_NO_STORE_RULE } from "./static_not_found_no_store.js";
11
12
  export { optionalFirebaseStubPlugin, optionalFirebaseStubId, optionalFirebaseStubCode, isModuleInstalled, OPTIONAL_FIREBASE_MODULES, OPTIONAL_FIREBASE_STUB_PREFIX, } from "./optional_firebase_stub.js";
12
13
  export { reactEvalStubPlugin, reactEvalEsbuildPlugin, transformReactEval, EVAL_WARNING_RE, EVAL_POLYFILL_SNIPPET, } from "./react_eval_stub.js";
13
14
  export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed, isVinextAppPageRouteWiringSafeOnDisk, patchAppPageBoundaryRender, isAppPageBoundaryRenderFile, isAppPageBoundaryRenderAlreadyFixed, isVinextAppPageBoundaryRenderSafeOnDisk, resolveVinextAppPageBoundaryRenderPath, patchAppPageProbe, isAppPageProbeFile, isAppPageProbeAlreadyFixed, isVinextAppPageProbeSafeOnDisk, syncPatchVinextOnDisk, type VinextRouteWiringFixPluginOptions, } from "./vinext_route_wiring_fix.js";
@@ -8,6 +8,7 @@ export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from ".
8
8
  export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
9
9
  export { bufferStubPlugin, BUFFER_STUB_ID } from "./buffer_stub.js";
10
10
  export { tailwindSourcesPlugin } from "./tailwind_sources_plugin.js";
11
+ export { staticNotFoundNoStorePlugin, appendStaticNoStoreRule, STATIC_NO_STORE_RULE } from "./static_not_found_no_store.js";
11
12
  export { optionalFirebaseStubPlugin, optionalFirebaseStubId, optionalFirebaseStubCode, isModuleInstalled, OPTIONAL_FIREBASE_MODULES, OPTIONAL_FIREBASE_STUB_PREFIX, } from "./optional_firebase_stub.js";
12
13
  export { reactEvalStubPlugin, reactEvalEsbuildPlugin, transformReactEval, EVAL_WARNING_RE, EVAL_POLYFILL_SNIPPET, } from "./react_eval_stub.js";
13
14
  export { vinextRouteWiringFixPlugin, patchAppPageRouteWiring, isAppPageRouteWiringFile, isAppPageRouteWiringAlreadyFixed, isVinextAppPageRouteWiringSafeOnDisk, patchAppPageBoundaryRender, isAppPageBoundaryRenderFile, isAppPageBoundaryRenderAlreadyFixed, isVinextAppPageBoundaryRenderSafeOnDisk, resolveVinextAppPageBoundaryRenderPath, patchAppPageProbe, isAppPageProbeFile, isAppPageProbeAlreadyFixed, isVinextAppPageProbeSafeOnDisk, syncPatchVinextOnDisk, } from "./vinext_route_wiring_fix.js";
@@ -17,6 +17,7 @@ export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
17
17
  userAgentStub?: boolean;
18
18
  cfWorkersClientStub?: boolean;
19
19
  tailwindSources?: boolean;
20
+ staticNotFoundNoStore?: boolean;
20
21
  bufferStub?: boolean;
21
22
  reactEvalStub?: boolean;
22
23
  optionalFirebaseStub?: boolean;
@@ -2,6 +2,7 @@ import { buildIdAsset } from "./build_id_asset.js";
2
2
  import { userAgentStubPlugin } from "./user_agent_stub.js";
3
3
  import { cfWorkersClientStubPlugin } from "./cf_workers_client_stub.js";
4
4
  import { tailwindSourcesPlugin } from "./tailwind_sources_plugin.js";
5
+ import { staticNotFoundNoStorePlugin } from "./static_not_found_no_store.js";
5
6
  import { bufferStubPlugin } from "./buffer_stub.js";
6
7
  import { reactEvalStubPlugin } from "./react_eval_stub.js";
7
8
  import { optionalFirebaseStubPlugin } from "./optional_firebase_stub.js";
@@ -85,6 +86,9 @@ export function cloudflareNextIntl(options = {}) {
85
86
  if (options.tailwindSources !== false) {
86
87
  plugins.push(tailwindSourcesPlugin());
87
88
  }
89
+ if (options.staticNotFoundNoStore !== false) {
90
+ plugins.push(staticNotFoundNoStorePlugin());
91
+ }
88
92
  if (options.userAgentStub !== false) {
89
93
  plugins.push(userAgentStubPlugin());
90
94
  }
@@ -0,0 +1,10 @@
1
+ import type { Plugin } from "vite";
2
+ export declare const STATIC_NO_STORE_RULE: {
3
+ source: string;
4
+ headers: {
5
+ key: string;
6
+ value: string;
7
+ }[];
8
+ };
9
+ export declare function appendStaticNoStoreRule(code: string): string;
10
+ export declare function staticNotFoundNoStorePlugin(): Plugin;
@@ -0,0 +1,19 @@
1
+ export const STATIC_NO_STORE_RULE = {
2
+ source: "/_next/static/:path*",
3
+ headers: [{ key: "Cache-Control", value: "no-store, must-revalidate" }],
4
+ };
5
+ const CONFIG_HEADERS_LITERAL = /(configHeaders:\s*|__configHeaders\s*=\s*)(\[[^\n]*\])(?=\s*[,;])/g;
6
+ export function appendStaticNoStoreRule(code) {
7
+ return code.replace(CONFIG_HEADERS_LITERAL, (_match, prefix, literal) => `${prefix}${JSON.stringify([...JSON.parse(literal), STATIC_NO_STORE_RULE])}`);
8
+ }
9
+ export function staticNotFoundNoStorePlugin() {
10
+ return {
11
+ name: "cfni:static-not-found-no-store",
12
+ transform(code, id) {
13
+ if (!id.includes("virtual:vinext-"))
14
+ return undefined;
15
+ const next = appendStaticNoStoreRule(code);
16
+ return next === code ? undefined : { code: next, map: null };
17
+ },
18
+ };
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.13.0",
3
+ "version": "0.13.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",
@@ -364,7 +364,7 @@
364
364
  "homepage": "https://github.com/demian-ilnytskyi/cloudflare-next-intl#readme",
365
365
  "dependencies": {
366
366
  "@microsoft/clarity": "^1.0.2",
367
- "cloudflare-next-intl-auth": "^0.2.1",
367
+ "cloudflare-next-intl-auth": "^0.3.0",
368
368
  "cloudflare-next-intl-db": "^0.3.0",
369
369
  "cloudflare-next-intl-db-codegen": "^0.1.4",
370
370
  "cloudflare-next-intl-errors": "^0.1.0",