cloudflare-next-intl 0.9.11 → 0.9.13

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
@@ -256,11 +256,12 @@ export default defineConfig({
256
256
  ```
257
257
 
258
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 sibling formats (`webp` by default; also supports `avif`, `png`, `jpeg`, `gif`, `tiff`, `heif`, `jp2`, `jxl`), generates 8px `.blur.webp` thumbnails with Next.js-matching SVG Gaussian blur placeholders, and provides transparent `<Image placeholder="blur" />` shimming via virtual modules. When more than one format is generated for an image, the shim renders a `<picture>` with one `<source>` per format ordered exactly as configured so the browser picks the best format it supports, with the original untouched file as an `onError` fallback if a generated asset fails to load. When the same image is used at different widths across the codebase, each size gets its own generated variant, and each `<Image>` usage automatically resolves to the closest matching size.
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`.
259
+ 1. **Auto Dynamic Pages for SSG (`autoDynamicPages`)**: Automatically scans your Next.js/Vinext App Router pages during Vite configuration (`configResolved`) and inserts `export const dynamic = "force-static"` for all static pages that do not access dynamic APIs. This ensures Vinext builds all public marketing and static pages into SSG HTML automatically without extra build scripts or manually writing `export const dynamic = "force-static"`.
260
+ 2. **Build-Time & Dev Image Optimizer (`imageOptimizer`)**: Automatically scans your image directories (`public/images`, `public/icons`), downscales oversized assets, produces sibling formats (`webp` by default; also supports `avif`, `png`, `jpeg`, `gif`, `tiff`, `heif`, `jp2`, `jxl`), generates 8px `.blur.webp` thumbnails with Next.js-matching SVG Gaussian blur placeholders, and provides transparent `<Image placeholder="blur" />` shimming via virtual modules. When more than one format is generated for an image, the shim renders a `<picture>` with one `<source>` per format — ordered exactly as configured — so the browser picks the best format it supports, with the original untouched file as an `onError` fallback if a generated asset fails to load. When the same image is used at different widths across the codebase, each size gets its own generated variant, and each `<Image>` usage automatically resolves to the closest matching size.
261
+ 3. **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.
262
+ 4. **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).
263
+ 5. **Cloudflare Workers Client Stub (`cfWorkersClientStub`)**: Stubs `cloudflare:workers` in client builds so shared modules can be referenced without client bundling errors.
264
+ 6. **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
 
265
266
  ##### Plugin Options
266
267
  All features are enabled by default, and can be individually configured or toggled off:
@@ -3,3 +3,5 @@ export declare const isBotCookieKey = "__is_bot_key__";
3
3
  export declare const isDarkCookieKey = "__is_dark_key__";
4
4
  export declare const cookieConsentCookieKey = "__cookie_consent_key__";
5
5
  export declare const privacyPolicyDateCookieKey = "__privacy_policy_date_key__";
6
+ export declare const countryCookieKey = "__cf_country__";
7
+ export declare const timezoneCookieKey = "__cf_timezone__";
@@ -3,3 +3,5 @@ export const isBotCookieKey = '__is_bot_key__';
3
3
  export const isDarkCookieKey = '__is_dark_key__';
4
4
  export const cookieConsentCookieKey = '__cookie_consent_key__';
5
5
  export const privacyPolicyDateCookieKey = '__privacy_policy_date_key__';
6
+ export const countryCookieKey = '__cf_country__';
7
+ export const timezoneCookieKey = '__cf_timezone__';
@@ -1,9 +1,10 @@
1
1
  import { NextResponse } from 'next/server.js';
2
2
  import { languageDetecotr } from '../server/functions/get_user_locale.js';
3
3
  import config from './intl_config.js';
4
- import { isBotCookieKey, localeCookieName } from './cookie_key.js';
4
+ import { countryCookieKey, isBotCookieKey, localeCookieName, timezoneCookieKey } from './cookie_key.js';
5
5
  import { cache } from 'react';
6
6
  import reportError from '../error_handling/report_error.js';
7
+ import { defaultCountryHeaderNames, defaultTimezoneHeaderNames } from '../server/functions/geo.js';
7
8
  const sameSite = false;
8
9
  const defaultCookieOption = {
9
10
  path: '/',
@@ -24,6 +25,14 @@ async function getIsBotValue(userAgent) {
24
25
  const getIsBotValueCache = cache(getIsBotValue);
25
26
  export const localesSet = new Set(config.locales);
26
27
  let updateSessionModule;
28
+ function getFirstHeaderValue(headers, names) {
29
+ for (const name of names) {
30
+ const value = headers.get(name);
31
+ if (value)
32
+ return value;
33
+ }
34
+ return undefined;
35
+ }
27
36
  export default async function intlMiddleware(request, options) {
28
37
  try {
29
38
  let initialChosenLocale;
@@ -59,14 +68,19 @@ export default async function intlMiddleware(request, options) {
59
68
  pathWithoutLocale = pathname;
60
69
  }
61
70
  const effectiveLocaleForRequest = urlLocale ?? initialChosenLocale;
62
- const country = request.cf?.country ?? request.headers.get('cf-ipcountry') ?? request.headers.get('x-cf-country');
63
- if (country) {
64
- request.headers.set('x-cf-country', country);
65
- }
66
- const timezone = request.cf?.timezone ?? request.headers.get('cf-timezone') ?? request.headers.get('x-cf-timezone');
67
- if (timezone) {
68
- request.headers.set('x-cf-timezone', timezone);
69
- }
71
+ const cf = request.cf;
72
+ const countryHeaderNames = config.cookieConsent?.countryHeaderNames
73
+ ?? config.generate?.countryHeaderNames
74
+ ?? defaultCountryHeaderNames;
75
+ const timezoneHeaderNames = config.generate?.timezoneHeaderNames
76
+ ?? defaultTimezoneHeaderNames;
77
+ const countryCookieName = config.cookieConsent?.countryCookieName
78
+ ?? config.generate?.countryCookieName
79
+ ?? countryCookieKey;
80
+ const timezoneCookieName = config.generate?.timezoneCookieName
81
+ ?? timezoneCookieKey;
82
+ const country = cf?.country || getFirstHeaderValue(request.headers, countryHeaderNames);
83
+ const timezone = cf?.timezone || getFirstHeaderValue(request.headers, timezoneHeaderNames);
70
84
  const requestHeaders = new Headers(request.headers);
71
85
  requestHeaders.set('x-pathname', pathWithoutLocale);
72
86
  requestHeaders.set('x-search', search);
@@ -123,9 +137,23 @@ export default async function intlMiddleware(request, options) {
123
137
  response.headers.set('x-search', search);
124
138
  if (country) {
125
139
  response.headers.set('x-cf-country', country);
140
+ response.cookies.set(countryCookieName, country, {
141
+ path: '/',
142
+ maxAge: 86400,
143
+ httpOnly: false,
144
+ secure: process.env.NODE_ENV === 'production',
145
+ sameSite: 'lax',
146
+ });
126
147
  }
127
148
  if (timezone) {
128
149
  response.headers.set('x-cf-timezone', timezone);
150
+ response.cookies.set(timezoneCookieName, timezone, {
151
+ path: '/',
152
+ maxAge: 86400,
153
+ httpOnly: false,
154
+ secure: process.env.NODE_ENV === 'production',
155
+ sameSite: 'lax',
156
+ });
129
157
  }
130
158
  if (!isRedirect && config.firebaseAuth && config.firebaseAuth.middlewareEnabled !== false) {
131
159
  if (!updateSessionModule) {
@@ -1,6 +1,6 @@
1
1
  import type { CookieConsentContextType } from '../types.js';
2
2
  export declare const CookieConsentContext: import("react").Context<CookieConsentContextType | null>;
3
- export default function CookieConsentProvider({ requiresConsent, children }: {
3
+ export default function CookieConsentProvider({ requiresConsent: requiresConsentProp, children }: {
4
4
  requiresConsent?: boolean;
5
5
  children: React.ReactNode;
6
6
  }): React.ReactElement;
@@ -6,7 +6,8 @@ import config from '../../config/intl_config.js';
6
6
  import requireCookieConsentConfig from '../require_config.js';
7
7
  import getCookie from '../../client/functions/get_cookie.js';
8
8
  import setCookie from '../../client/functions/set_cookie.js';
9
- import { cookieConsentCookieKey, privacyPolicyDateCookieKey } from '../../config/cookie_key.js';
9
+ import { cookieConsentCookieKey, countryCookieKey, privacyPolicyDateCookieKey } from '../../config/cookie_key.js';
10
+ import { defaultGdprCountries } from '../gdpr_countries.js';
10
11
  export const CookieConsentContext = createContext(null);
11
12
  function parseConsent(raw) {
12
13
  if (raw === 'true')
@@ -15,8 +16,9 @@ function parseConsent(raw) {
15
16
  return false;
16
17
  return null;
17
18
  }
18
- export default function CookieConsentProvider({ requiresConsent = true, children }) {
19
- const { consentCookieName, dateCookieName, maxAge, policyDate, privacyPolicyPath, showPrivacyPolicy } = useMemo(() => {
19
+ const defaultGdprCountriesSet = new Set(defaultGdprCountries);
20
+ export default function CookieConsentProvider({ requiresConsent: requiresConsentProp = true, children }) {
21
+ const { consentCookieName, dateCookieName, maxAge, policyDate, privacyPolicyPath, showPrivacyPolicy, gdprCountries } = useMemo(() => {
20
22
  const cc = requireCookieConsentConfig(config.cookieConsent);
21
23
  return {
22
24
  consentCookieName: cc.consentCookieName ?? cookieConsentCookieKey,
@@ -25,17 +27,28 @@ export default function CookieConsentProvider({ requiresConsent = true, children
25
27
  policyDate: cc.privacyPolicyDate ? new Date(cc.privacyPolicyDate) : null,
26
28
  privacyPolicyPath: cc.privacyPolicyPath ?? '/privacy-policy',
27
29
  showPrivacyPolicy: cc.showPrivacyPolicy ?? true,
30
+ gdprCountries: cc.gdprCountries,
28
31
  };
29
32
  }, []);
30
33
  const [consent, setConsentState] = useState(null);
31
34
  const [privacyPolicyUpdated, setPrivacyPolicyUpdated] = useState(false);
32
35
  const [isMounted, setIsMounted] = useState(false);
36
+ const [requiresConsent, setRequiresConsent] = useState(requiresConsentProp);
33
37
  const pathname = usePathname();
34
38
  useEffect(() => {
35
39
  const rawConsent = getCookie(consentCookieName);
36
40
  const storedConsent = parseConsent(rawConsent);
37
41
  const isFirstVisit = rawConsent === null;
38
42
  setConsentState(storedConsent);
43
+ if (requiresConsentProp) {
44
+ const countryCookie = getCookie(countryCookieKey);
45
+ if (countryCookie) {
46
+ const gdprSet = gdprCountries
47
+ ? new Set(gdprCountries)
48
+ : defaultGdprCountriesSet;
49
+ setRequiresConsent(gdprSet.has(countryCookie));
50
+ }
51
+ }
39
52
  setIsMounted(true);
40
53
  if (isFirstVisit || !policyDate)
41
54
  return;
@@ -46,7 +59,7 @@ export default function CookieConsentProvider({ requiresConsent = true, children
46
59
  }
47
60
  const storedDate = new Date(storedDateRaw);
48
61
  setPrivacyPolicyUpdated(!Number.isNaN(storedDate.getTime()) && storedDate < policyDate);
49
- }, [consentCookieName, dateCookieName, maxAge, policyDate]);
62
+ }, [consentCookieName, dateCookieName, maxAge, policyDate, requiresConsentProp, gdprCountries]);
50
63
  const setConsent = useCallback((value) => {
51
64
  if (value === null) {
52
65
  setCookie({ name: consentCookieName, value: 'null', maxAge });
@@ -4,7 +4,6 @@ import { getMessage } from "../functions/server.js";
4
4
  import dynamic from "next/dynamic.js";
5
5
  import { localesSet } from "../../config/middleware.js";
6
6
  import config from "../../config/intl_config.js";
7
- import resolveRequiresConsent from "../../cookie_consent/gdpr_countries.js";
8
7
  import installConsoleErrorOverride from "../../error_handling/install_console_error_override.js";
9
8
  import reportError from "../../error_handling/report_error.js";
10
9
  const LocationzationClientProvider = dynamic(() => import("../../client/components/client_provider.js"));
@@ -43,9 +42,7 @@ export default async function LocationzationProvider({ language, messages, stati
43
42
  let requiresConsent = true;
44
43
  if (config.cookieConsent) {
45
44
  const isDevEnvironment = process.env.NODE_ENV === 'development';
46
- requiresConsent = !isDevEnvironment
47
- ? await resolveRequiresConsent(config.cookieConsent.getCountryCode, config.generate?.getCloudflareContext, config.cookieConsent.gdprCountries, config.errorHandling, config.cookieConsent.countryHeaderNames, config.generate)
48
- : false;
45
+ requiresConsent = !isDevEnvironment;
49
46
  const analyticsAllowedInEnv = config.cookieConsent.enableAnalyticsInDevMode === true || !isDevEnvironment;
50
47
  if (config.cookieConsent.autoWireAnalytics !== false && analyticsAllowedInEnv) {
51
48
  if (config.cookieConsent.getAnalytics) {
@@ -4,7 +4,6 @@ import { getMessage } from "../functions/server.js";
4
4
  import dynamic from "next/dynamic.js";
5
5
  import { localesSet } from "../../config/middleware.js";
6
6
  import config from "../../config/intl_config.js";
7
- import resolveRequiresConsent from "../../cookie_consent/gdpr_countries.js";
8
7
  import installConsoleErrorOverride from "../../error_handling/install_console_error_override.js";
9
8
  import reportError from "../../error_handling/report_error.js";
10
9
  const LocationzationClientProvider = dynamic(() => import("../../client/components/client_provider_static.js"));
@@ -32,9 +31,7 @@ export default async function LocationzationProvider({ language, messages, child
32
31
  let requiresConsent = true;
33
32
  if (config.cookieConsent) {
34
33
  const isDevEnvironment = process.env.NODE_ENV === 'development';
35
- requiresConsent = !isDevEnvironment
36
- ? await resolveRequiresConsent(config.cookieConsent.getCountryCode, config.generate?.getCloudflareContext, config.cookieConsent.gdprCountries, config.errorHandling, config.cookieConsent.countryHeaderNames, config.generate)
37
- : false;
34
+ requiresConsent = !isDevEnvironment;
38
35
  const analyticsAllowedInEnv = config.cookieConsent.enableAnalyticsInDevMode === true || !isDevEnvironment;
39
36
  if (config.cookieConsent.autoWireAnalytics !== false && analyticsAllowedInEnv) {
40
37
  if (config.cookieConsent.getAnalytics) {
@@ -1,3 +1,4 @@
1
+ import { countryCookieKey, timezoneCookieKey } from '../../config/cookie_key.js';
1
2
  export const defaultCountryHeaderNames = ['x-cf-country', 'cf-ipcountry'];
2
3
  export const defaultTimezoneHeaderNames = ['x-cf-timezone', 'cf-timezone'];
3
4
  function extractHeader(h, name) {
@@ -26,6 +27,28 @@ function extractFromHeaderNames(h, headerNames) {
26
27
  }
27
28
  return undefined;
28
29
  }
30
+ function extractCookieHeader(h, name) {
31
+ const cookieHeader = extractHeader(h, 'cookie');
32
+ if (!cookieHeader)
33
+ return undefined;
34
+ const parts = cookieHeader.split(';');
35
+ for (const part of parts) {
36
+ const index = part.indexOf('=');
37
+ if (index === -1)
38
+ continue;
39
+ const key = part.slice(0, index).trim();
40
+ if (key !== name)
41
+ continue;
42
+ const value = part.slice(index + 1).trim();
43
+ return value ? decodeURIComponent(value) : undefined;
44
+ }
45
+ return undefined;
46
+ }
47
+ function extractCookie(cookieStore, name) {
48
+ const cookie = cookieStore?.get?.(name);
49
+ const value = typeof cookie === 'string' ? cookie : cookie?.value;
50
+ return value || undefined;
51
+ }
29
52
  export async function getCountry(input, generate, headerNames) {
30
53
  const gen = generate ?? await configuredGenerate();
31
54
  const names = headerNames
@@ -36,11 +59,21 @@ export async function getCountry(input, generate, headerNames) {
36
59
  const country = extractFromHeaderNames(input.headers, names);
37
60
  if (country)
38
61
  return country;
62
+ const cookieCountry = extractCookieHeader(input.headers, countryCookieKey);
63
+ if (cookieCountry)
64
+ return cookieCountry;
39
65
  }
40
66
  else if (typeof input.get === 'function') {
41
67
  const country = extractFromHeaderNames(input, names);
42
68
  if (country)
43
69
  return country;
70
+ const cookieCountry = extractCookieHeader(input, countryCookieKey);
71
+ if (cookieCountry)
72
+ return cookieCountry;
73
+ }
74
+ const cookieCountry = extractCookie(input.cookies, countryCookieKey);
75
+ if (cookieCountry) {
76
+ return cookieCountry;
44
77
  }
45
78
  const cf = input.cf;
46
79
  if (cf?.country && typeof cf.country === 'string' && cf.country.length > 0) {
@@ -53,6 +86,18 @@ export async function getCountry(input, generate, headerNames) {
53
86
  const country = extractFromHeaderNames(h, names);
54
87
  if (country)
55
88
  return country;
89
+ const cookieCountry = extractCookieHeader(h, countryCookieKey);
90
+ if (cookieCountry)
91
+ return cookieCountry;
92
+ }
93
+ catch {
94
+ }
95
+ try {
96
+ const { cookies } = await import('next/headers.js');
97
+ const c = await cookies();
98
+ const country = extractCookie(c, countryCookieKey);
99
+ if (country)
100
+ return country;
56
101
  }
57
102
  catch {
58
103
  }
@@ -89,11 +134,21 @@ export async function getTimezone(input, fallback, generate, headerNames) {
89
134
  const tz = extractFromHeaderNames(input.headers, names);
90
135
  if (tz)
91
136
  return tz;
137
+ const cookieTimezone = extractCookieHeader(input.headers, timezoneCookieKey);
138
+ if (cookieTimezone)
139
+ return cookieTimezone;
92
140
  }
93
141
  else if (typeof input.get === 'function') {
94
142
  const tz = extractFromHeaderNames(input, names);
95
143
  if (tz)
96
144
  return tz;
145
+ const cookieTimezone = extractCookieHeader(input, timezoneCookieKey);
146
+ if (cookieTimezone)
147
+ return cookieTimezone;
148
+ }
149
+ const cookieTimezone = extractCookie(input.cookies, timezoneCookieKey);
150
+ if (cookieTimezone) {
151
+ return cookieTimezone;
97
152
  }
98
153
  const cf = input.cf;
99
154
  if (cf?.timezone && typeof cf.timezone === 'string' && cf.timezone.length > 0) {
@@ -106,6 +161,18 @@ export async function getTimezone(input, fallback, generate, headerNames) {
106
161
  const tz = extractFromHeaderNames(h, names);
107
162
  if (tz)
108
163
  return tz;
164
+ const cookieTimezone = extractCookieHeader(h, timezoneCookieKey);
165
+ if (cookieTimezone)
166
+ return cookieTimezone;
167
+ }
168
+ catch {
169
+ }
170
+ try {
171
+ const { cookies } = await import('next/headers.js');
172
+ const c = await cookies();
173
+ const tz = extractCookie(c, timezoneCookieKey);
174
+ if (tz)
175
+ return tz;
109
176
  }
110
177
  catch {
111
178
  }
@@ -29,10 +29,17 @@ export interface GenerateRoutingConfig {
29
29
  } | undefined);
30
30
  getCloudflareContext?: CookieConsentGetCloudflareContext;
31
31
  countryHeaderNames?: readonly string[];
32
+ countryCookieName?: string;
32
33
  timezoneHeaderNames?: readonly string[];
34
+ timezoneCookieName?: string;
33
35
  }
34
36
  export type RequestOrHeaders = Request | Headers | {
35
37
  headers?: Headers | Record<string, string | null | undefined>;
38
+ cookies?: {
39
+ get?: (name: string) => {
40
+ value?: string;
41
+ } | string | undefined;
42
+ };
36
43
  cf?: {
37
44
  country?: string;
38
45
  timezone?: string;
@@ -75,6 +82,7 @@ export interface CookieConsentRoutingConfig {
75
82
  autoAnalyticsEvents?: AutoAnalyticsEventsConfig;
76
83
  getCountryCode?: () => string | undefined | Promise<string | undefined>;
77
84
  countryHeaderNames?: readonly string[];
85
+ countryCookieName?: string;
78
86
  gdprCountries?: readonly string[];
79
87
  enableAnalyticsInDevMode?: boolean;
80
88
  autoWireDialogs?: boolean;
@@ -0,0 +1,8 @@
1
+ import type { Plugin } from "vite";
2
+ import { type DynamicPagesCheckMode } from "../dynamic_pages_check/index.js";
3
+ export interface AutoDynamicPagesPluginOptions {
4
+ appDir?: string;
5
+ mode?: DynamicPagesCheckMode;
6
+ target?: 'next' | 'vinext';
7
+ }
8
+ export declare function autoDynamicPagesPlugin(options?: AutoDynamicPagesPluginOptions): Plugin;
@@ -0,0 +1,38 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { checkDynamicPages } from "../dynamic_pages_check/index.js";
4
+ export function autoDynamicPagesPlugin(options = {}) {
5
+ let ran = false;
6
+ return {
7
+ name: "cloudflare-next-intl-auto-dynamic-pages",
8
+ enforce: "pre",
9
+ async configResolved(config) {
10
+ if (ran)
11
+ return;
12
+ ran = true;
13
+ const root = config.root || process.cwd();
14
+ let appDir = options.appDir;
15
+ if (!appDir) {
16
+ if (existsSync(resolve(root, "src/app"))) {
17
+ appDir = resolve(root, "src/app");
18
+ }
19
+ else if (existsSync(resolve(root, "app"))) {
20
+ appDir = resolve(root, "app");
21
+ }
22
+ }
23
+ if (!appDir || !existsSync(appDir)) {
24
+ return;
25
+ }
26
+ try {
27
+ await checkDynamicPages({
28
+ appDir,
29
+ mode: options.mode ?? "fix",
30
+ target: options.target ?? "vinext",
31
+ });
32
+ }
33
+ catch (err) {
34
+ console.warn("[cloudflare-next-intl] autoDynamicPages check error:", err);
35
+ }
36
+ },
37
+ };
38
+ }
@@ -1,3 +1,4 @@
1
+ export { autoDynamicPagesPlugin, type AutoDynamicPagesPluginOptions } from "./auto_dynamic_pages_plugin.js";
1
2
  export { buildIdAsset } from "./build_id_asset.js";
2
3
  export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
3
4
  export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
@@ -1,3 +1,4 @@
1
+ export { autoDynamicPagesPlugin } from "./auto_dynamic_pages_plugin.js";
1
2
  export { buildIdAsset } from "./build_id_asset.js";
2
3
  export { userAgentStubPlugin, USER_AGENT_STUB_ID, USER_AGENT_STUB_CODE } from "./user_agent_stub.js";
3
4
  export { cfWorkersClientStubPlugin, CF_WORKERS_CLIENT_STUB_ID, CF_WORKERS_CLIENT_STUB_CODE } from "./cf_workers_client_stub.js";
@@ -1,7 +1,9 @@
1
1
  import type { Plugin } from "vite";
2
2
  import { type LocaleFilePluginOptions } from "./locale_file_plugin.js";
3
3
  import { type ImageOptimizerPluginOptions } from "../image_optimizer/index.js";
4
+ import { type AutoDynamicPagesPluginOptions } from "./auto_dynamic_pages_plugin.js";
4
5
  export interface CloudflareNextIntlOptions extends LocaleFilePluginOptions {
6
+ autoDynamicPages?: boolean | AutoDynamicPagesPluginOptions;
5
7
  buildIdAsset?: boolean | string;
6
8
  localeFiles?: boolean;
7
9
  userAgentStub?: boolean;
@@ -3,8 +3,14 @@ import { userAgentStubPlugin } from "./user_agent_stub.js";
3
3
  import { cfWorkersClientStubPlugin } from "./cf_workers_client_stub.js";
4
4
  import { localeFilePlugin } from "./locale_file_plugin.js";
5
5
  import { imageOptimizerPlugin } from "../image_optimizer/index.js";
6
+ import { autoDynamicPagesPlugin } from "./auto_dynamic_pages_plugin.js";
6
7
  export function cloudflareNextIntl(options = {}) {
7
8
  const plugins = [];
9
+ if (options.autoDynamicPages !== false) {
10
+ plugins.push(autoDynamicPagesPlugin(typeof options.autoDynamicPages === "object"
11
+ ? options.autoDynamicPages
12
+ : undefined));
13
+ }
8
14
  if (options.imageOptimizer !== false) {
9
15
  plugins.push(imageOptimizerPlugin(typeof options.imageOptimizer === "object"
10
16
  ? options.imageOptimizer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.9.11",
3
+ "version": "0.9.13",
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",