gt-next 11.1.24 → 11.2.0
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/CHANGELOG.md +43 -0
- package/dist/middleware-dir/createNextMiddleware.d.ts +7 -3
- package/dist/middleware-dir/createNextMiddleware.d.ts.map +1 -1
- package/dist/middleware-dir/createNextMiddleware.js +32 -12
- package/dist/middleware-dir/createNextMiddleware.js.map +1 -1
- package/dist/middleware-dir/createNextMiddleware.mjs +32 -12
- package/dist/middleware-dir/createNextMiddleware.mjs.map +1 -1
- package/dist/middleware-dir/createPathMatcher.d.ts +36 -0
- package/dist/middleware-dir/createPathMatcher.d.ts.map +1 -0
- package/dist/middleware-dir/createPathMatcher.js +84 -0
- package/dist/middleware-dir/createPathMatcher.js.map +1 -0
- package/dist/middleware-dir/createPathMatcher.mjs +79 -0
- package/dist/middleware-dir/createPathMatcher.mjs.map +1 -0
- package/dist/middleware-dir/matchPath.d.ts +7 -0
- package/dist/middleware-dir/matchPath.d.ts.map +1 -0
- package/dist/middleware-dir/matchPath.js +104 -0
- package/dist/middleware-dir/matchPath.js.map +1 -0
- package/dist/middleware-dir/matchPath.mjs +103 -0
- package/dist/middleware-dir/matchPath.mjs.map +1 -0
- package/dist/middleware-dir/pathname.d.ts +7 -0
- package/dist/middleware-dir/pathname.d.ts.map +1 -0
- package/dist/middleware-dir/pathname.js +27 -0
- package/dist/middleware-dir/pathname.js.map +1 -0
- package/dist/middleware-dir/pathname.mjs +24 -0
- package/dist/middleware-dir/pathname.mjs.map +1 -0
- package/dist/middleware-dir/utils.d.ts +11 -26
- package/dist/middleware-dir/utils.d.ts.map +1 -1
- package/dist/middleware-dir/utils.js +45 -70
- package/dist/middleware-dir/utils.js.map +1 -1
- package/dist/middleware-dir/utils.mjs +42 -69
- package/dist/middleware-dir/utils.mjs.map +1 -1
- package/dist/middleware.d.ts +1 -0
- package/dist/middleware.d.ts.map +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# gt-next
|
|
2
2
|
|
|
3
|
+
## 11.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2274](https://github.com/generaltranslation/gt/pull/2274) [`68dee63`](https://github.com/generaltranslation/gt/commit/68dee639226162c690ace5ae7ae3b61e399d8984) Thanks [@eoinest](https://github.com/eoinest)! - Add locale-specific page overrides through `routeOverrides`, independently of localized paths in `pathConfig`.
|
|
8
|
+
- `routeOverrides` maps each locale to shared path patterns, so `fr: ['/proj/[id]']` renders `app/[locale]/fr/proj/[id]/page.tsx`, even when its public path is localized through `pathConfig`.
|
|
9
|
+
- When using `revalidatePath`, pass the internal rewrite destination rather than the public URL.
|
|
10
|
+
- `useSelectedLayoutSegments` reflects the internal route structure, including the override's locale directory when called from the `[locale]` layout.
|
|
11
|
+
- On Next.js 16.3.x, prefetching links whose URLs redirect can cause repeated requests; [upstream reports a fix in 16.4.0-canary.6](https://github.com/vercel/next.js/issues/97329#issuecomment-5515713671), which has not yet been verified against the gt-next reproduction.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [#2274](https://github.com/generaltranslation/gt/pull/2274) [`804eb64`](https://github.com/generaltranslation/gt/commit/804eb640edb8237fcd617bc7a03b014e393529e2) Thanks [@eoinest](https://github.com/eoinest)! - Treat static text in configured dynamic routes literally when matching shared paths and localized aliases. Escape regex metacharacters such as `.`, `+`, parentheses, `|`, and `$` so valid routes match and lookalike paths do not, including unprefixed default-locale aliases used for locale selection.
|
|
16
|
+
|
|
17
|
+
- [#2274](https://github.com/generaltranslation/gt/pull/2274) [`c68e776`](https://github.com/generaltranslation/gt/commit/c68e776d055e47780c80faa01c00c445314706bd) Thanks [@eoinest](https://github.com/eoinest)! - Support catch-all middleware paths and index dynamic route matching by segment.
|
|
18
|
+
|
|
19
|
+
Keep locale prefixes out of shared route parameters, preserve static route precedence across localized and shared paths, and support dotted parameter names.
|
|
20
|
+
|
|
21
|
+
Preserve the source route when switching from a locale alias and remove the target default-locale prefix correctly.
|
|
22
|
+
|
|
23
|
+
Resolve bare locale roots such as `/fr` to configured homepage aliases while preserving the request's trailing-slash style.
|
|
24
|
+
|
|
25
|
+
- [#2244](https://github.com/generaltranslation/gt/pull/2244) [`f360400`](https://github.com/generaltranslation/gt/commit/f3604003ef17388e56d72877438dc7cc988598b8) Thanks [@eoinest](https://github.com/eoinest)! - Match encoded request paths against normalized Unicode middleware paths.
|
|
26
|
+
|
|
27
|
+
Preserve encoded dynamic parameter values during redirects and rewrites, decode static lookup keys only once, and avoid redirect loops for encoded or decomposed Unicode path templates.
|
|
28
|
+
|
|
29
|
+
Keep percent-encoded bracket literals distinct from dynamic route syntax.
|
|
30
|
+
|
|
31
|
+
- [#2246](https://github.com/generaltranslation/gt/pull/2246) [`a38e6d0`](https://github.com/generaltranslation/gt/commit/a38e6d06c319233c2a1a4b560b476188e15208f3) Thanks [@eoinest](https://github.com/eoinest)! - Preserve the configured Next.js base path in middleware rewrites and redirects.
|
|
32
|
+
|
|
33
|
+
Handle app routes and locale prefixes that repeat the base path without dropping a segment or redirecting to themselves.
|
|
34
|
+
|
|
35
|
+
Preserve the request's trailing-slash style when redirecting to the base-path root, avoiding an extra slash-normalization redirect.
|
|
36
|
+
|
|
37
|
+
- [#2245](https://github.com/generaltranslation/gt/pull/2245) [`f310cdd`](https://github.com/generaltranslation/gt/commit/f310cdda19fe5a2196e0f1d0ed3d0b528354c1ef) Thanks [@eoinest](https://github.com/eoinest)! - Match middleware paths with trailing slashes and preserve them in rewrites.
|
|
38
|
+
|
|
39
|
+
- Updated dependencies []:
|
|
40
|
+
- generaltranslation@9.3.1
|
|
41
|
+
- @generaltranslation/compiler@1.3.51
|
|
42
|
+
- gt-i18n@1.0.26
|
|
43
|
+
- gt-react@11.2.0
|
|
44
|
+
- @generaltranslation/react-core@11.2.0
|
|
45
|
+
|
|
3
46
|
## 11.1.24
|
|
4
47
|
|
|
5
48
|
### Patch Changes
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import type { NextRequest } from 'next/server';
|
|
3
|
+
import { type PathConfig } from './utils';
|
|
4
|
+
export type RouteOverrides = Record<string, readonly string[]>;
|
|
3
5
|
/**
|
|
4
6
|
* Middleware factory to create a Next.js middleware for i18n routing and locale detection.
|
|
5
7
|
*
|
|
@@ -12,12 +14,14 @@ import { PathConfig } from './utils';
|
|
|
12
14
|
* @param {boolean} [config.prefixDefaultLocale=false] - Flag to enable or disable prefixing the default locale to the pathname, i.e., /en/about -> /about
|
|
13
15
|
* @param {boolean} [config.ignoreSourceMaps=true] - Flag to enable or disable ignoring source maps
|
|
14
16
|
* @param {PathConfig} [config.pathConfig] - Path configuration for locale routing
|
|
17
|
+
* @param {RouteOverrides} [config.routeOverrides] - Locale-relative paths to rewrite from /{locale}/{path} to /{locale}/{locale}/{path}
|
|
15
18
|
* @returns {function} - A middleware function that processes the request and response.
|
|
16
19
|
*/
|
|
17
|
-
export declare function createNextMiddleware({ localeRouting, prefixDefaultLocale, ignoreSourceMaps, pathConfig, }?: {
|
|
20
|
+
export declare function createNextMiddleware({ localeRouting, prefixDefaultLocale, ignoreSourceMaps, pathConfig, routeOverrides, }?: {
|
|
18
21
|
localeRouting?: boolean;
|
|
19
22
|
prefixDefaultLocale?: boolean;
|
|
20
23
|
ignoreSourceMaps?: boolean;
|
|
21
24
|
pathConfig?: PathConfig;
|
|
25
|
+
routeOverrides?: RouteOverrides;
|
|
22
26
|
}): (req: NextRequest) => NextResponse<unknown>;
|
|
23
27
|
//# sourceMappingURL=createNextMiddleware.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createNextMiddleware.d.ts","sourceRoot":"","sources":["../../src/middleware-dir/createNextMiddleware.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createNextMiddleware.d.ts","sourceRoot":"","sources":["../../src/middleware-dir/createNextMiddleware.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAS/C,OAAO,EASL,KAAK,UAAU,EAEhB,MAAM,SAAS,CAAC;AAejB,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;AAE/D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,aAAoB,EACpB,mBAA2B,EAC3B,gBAAuB,EACvB,UAAe,EACf,cAAmB,GACpB,GAAE;IACD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,cAAc,CAAC,EAAE,cAAc,CAAC;CAC5B,SAqIqB,WAAW,2BA0PrC"}
|
|
@@ -3,7 +3,10 @@ require("../_virtual/_rolldown/runtime.js");
|
|
|
3
3
|
require("../utils/headers.js");
|
|
4
4
|
require("../utils/cookies.js");
|
|
5
5
|
const require_utils_pathRegex = require("../utils/pathRegex.js");
|
|
6
|
+
const require_middleware_dir_pathname = require("./pathname.js");
|
|
6
7
|
const require_errors_middleware = require("../errors/middleware.js");
|
|
8
|
+
const require_middleware_dir_createPathMatcher = require("./createPathMatcher.js");
|
|
9
|
+
const require_middleware_dir_matchPath = require("./matchPath.js");
|
|
7
10
|
const require_middleware_dir_utils = require("./utils.js");
|
|
8
11
|
let generaltranslation_internal = require("generaltranslation/internal");
|
|
9
12
|
let _generaltranslation_react_core_pure = require("@generaltranslation/react-core/pure");
|
|
@@ -24,9 +27,10 @@ const NEXT_JS_SOURCE_MAP_PATH = "/__nextjs_source-map";
|
|
|
24
27
|
* @param {boolean} [config.prefixDefaultLocale=false] - Flag to enable or disable prefixing the default locale to the pathname, i.e., /en/about -> /about
|
|
25
28
|
* @param {boolean} [config.ignoreSourceMaps=true] - Flag to enable or disable ignoring source maps
|
|
26
29
|
* @param {PathConfig} [config.pathConfig] - Path configuration for locale routing
|
|
30
|
+
* @param {RouteOverrides} [config.routeOverrides] - Locale-relative paths to rewrite from /{locale}/{path} to /{locale}/{locale}/{path}
|
|
27
31
|
* @returns {function} - A middleware function that processes the request and response.
|
|
28
32
|
*/
|
|
29
|
-
function createNextMiddleware({ localeRouting = true, prefixDefaultLocale = false, ignoreSourceMaps = true, pathConfig = {} } = {}) {
|
|
33
|
+
function createNextMiddleware({ localeRouting = true, prefixDefaultLocale = false, ignoreSourceMaps = true, pathConfig = {}, routeOverrides = {} } = {}) {
|
|
30
34
|
const pathRegex = require_utils_pathRegex.compilePathRegex(process.env._GENERALTRANSLATION_PATH_REGEX);
|
|
31
35
|
let envParams;
|
|
32
36
|
if (process.env._GENERALTRANSLATION_I18N_CONFIG_PARAMS) try {
|
|
@@ -57,7 +61,15 @@ function createNextMiddleware({ localeRouting = true, prefixDefaultLocale = fals
|
|
|
57
61
|
}, {});
|
|
58
62
|
return acc;
|
|
59
63
|
}, {});
|
|
60
|
-
|
|
64
|
+
routeOverrides = Object.entries(routeOverrides).reduce((acc, [locale, paths]) => {
|
|
65
|
+
acc[gtServicesEnabled ? (0, _generaltranslation_format.standardizeLocale)(locale) : locale] = paths;
|
|
66
|
+
return acc;
|
|
67
|
+
}, {});
|
|
68
|
+
const routeOverridePathMaps = Object.entries(routeOverrides).reduce((acc, [locale, paths]) => {
|
|
69
|
+
acc[locale] = require_middleware_dir_createPathMatcher.createPathToSharedPathMap(Object.fromEntries(paths.map((path) => [path, path])), true, defaultLocale).pathToSharedPath;
|
|
70
|
+
return acc;
|
|
71
|
+
}, {});
|
|
72
|
+
const { pathToSharedPath, defaultLocalePaths } = require_middleware_dir_createPathMatcher.createPathToSharedPathMap(pathConfig, prefixDefaultLocale, defaultLocale);
|
|
61
73
|
/**
|
|
62
74
|
* Processes the incoming request to determine the user's locale and sets a locale cookie.
|
|
63
75
|
* Optionally redirects the user based on the locale if locale-based routing is enabled.
|
|
@@ -102,27 +114,35 @@ function createNextMiddleware({ localeRouting = true, prefixDefaultLocale = fals
|
|
|
102
114
|
});
|
|
103
115
|
if (localeRouting) {
|
|
104
116
|
const { pathname } = req.nextUrl;
|
|
105
|
-
const standardizedPathname = pathnameLocale && pathnameLocale !== unstandardizedPathnameLocale ? pathname.replace(new RegExp(`^/${unstandardizedPathnameLocale}`), `/${
|
|
106
|
-
const
|
|
107
|
-
const
|
|
117
|
+
const standardizedPathname = pathnameLocale && pathnameLocale !== unstandardizedPathnameLocale ? pathname.replace(new RegExp(`^/${unstandardizedPathnameLocale}`), `/${pathnameLocale}`) : pathname;
|
|
118
|
+
const sharedPathMatch = require_middleware_dir_matchPath.getSharedPath(standardizedPathname, pathToSharedPath, pathnameLocale);
|
|
119
|
+
const sharedPath = sharedPathMatch?.sharedPath;
|
|
120
|
+
const sharedPathWithParameters = sharedPathMatch !== void 0 ? require_middleware_dir_pathname.applyTrailingSlash(standardizedPathname, require_middleware_dir_utils.replaceDynamicSegments(sharedPathMatch.matchedPathname, `/${userLocale}${sharedPath}`, sharedPathMatch.pathTemplate)) : void 0;
|
|
108
121
|
const localizedPath = sharedPath !== void 0 ? require_middleware_dir_utils.getLocalizedPath(sharedPath, userLocale, pathConfig) : void 0;
|
|
109
|
-
const localizedPathWithParameters = localizedPath !== void 0 ? require_middleware_dir_utils.replaceDynamicSegments(
|
|
122
|
+
const localizedPathWithParameters = localizedPath !== void 0 && sharedPathMatch !== void 0 ? require_middleware_dir_pathname.applyTrailingSlash(standardizedPathname, require_middleware_dir_utils.replaceDynamicSegments(sharedPathMatch.matchedPathname, localizedPath, sharedPathMatch.pathTemplate)) : void 0;
|
|
123
|
+
const pagePath = (sharedPathWithParameters?.replace(new RegExp(`^/${userLocale}`), "") ?? (pathnameLocale ? standardizedPathname.replace(new RegExp(`^/${userLocale}`), "") : standardizedPathname)) || "/";
|
|
124
|
+
const routeOverridePathMap = routeOverridePathMaps[userLocale];
|
|
125
|
+
const routeOverridePath = (routeOverridePathMap ? require_middleware_dir_matchPath.getSharedPath(pagePath, routeOverridePathMap, void 0) : void 0) !== void 0 ? require_middleware_dir_pathname.applyTrailingSlash(standardizedPathname, `/${userLocale}/${userLocale}${pagePath === "/" ? "" : pagePath}`) : void 0;
|
|
110
126
|
if (localizedPathWithParameters === void 0) {
|
|
111
127
|
if (!prefixDefaultLocale && (0, _generaltranslation_format.isSameDialect)(userLocale, defaultLocale)) if (pathnameLocale) {
|
|
112
128
|
if (clearResetCookie) return getRedirectResponse(pathname.replace(new RegExp(`^/${unstandardizedPathnameLocale}`), ``) || "/");
|
|
113
|
-
} else return getRewriteResponse(`/${userLocale}${pathname}`);
|
|
129
|
+
} else return getRewriteResponse(routeOverridePath || `/${userLocale}${pathname}`);
|
|
114
130
|
else if (!pathnameLocale) return getRedirectResponse(`/${userLocale}${pathname}`);
|
|
115
131
|
if (pathnameLocale && userLocale !== unstandardizedPathnameLocale) return getRedirectResponse(pathname.replace(new RegExp(`^/${unstandardizedPathnameLocale}`), `/${userLocale}`));
|
|
132
|
+
if (routeOverridePath) return getRewriteResponse(routeOverridePath);
|
|
116
133
|
return getNextResponse();
|
|
117
134
|
}
|
|
118
135
|
if (!prefixDefaultLocale && (0, _generaltranslation_format.isSameDialect)(userLocale, defaultLocale)) if (pathnameLocale) {
|
|
119
|
-
if (clearResetCookie) return getRedirectResponse(localizedPathWithParameters.replace(new RegExp(`^/${
|
|
136
|
+
if (clearResetCookie) return getRedirectResponse(localizedPathWithParameters.replace(new RegExp(`^/${userLocale}`), ``) || "/");
|
|
120
137
|
} else {
|
|
121
|
-
|
|
122
|
-
return
|
|
138
|
+
const localizedPublicPath = localizedPathWithParameters.replace(new RegExp(`^/${userLocale}`), "") || "/";
|
|
139
|
+
if (!pathnameLocale && require_middleware_dir_pathname.normalizePathname(localizedPublicPath) !== require_middleware_dir_pathname.normalizePathname(pathname)) return getRedirectResponse(localizedPublicPath);
|
|
140
|
+
return getRewriteResponse(routeOverridePath || sharedPathWithParameters);
|
|
123
141
|
}
|
|
124
|
-
if (pathname !== localizedPathWithParameters) return getRedirectResponse(localizedPathWithParameters);
|
|
125
|
-
if (
|
|
142
|
+
if (require_middleware_dir_pathname.normalizePathname(pathname) !== require_middleware_dir_pathname.normalizePathname(localizedPathWithParameters)) return getRedirectResponse(localizedPathWithParameters);
|
|
143
|
+
if (routeOverridePath) return getRewriteResponse(routeOverridePath);
|
|
144
|
+
const rewriteUrl = new URL(sharedPathWithParameters, req.nextUrl);
|
|
145
|
+
if (req.nextUrl.pathname !== rewriteUrl.pathname) return getRewriteResponse(sharedPathWithParameters);
|
|
126
146
|
}
|
|
127
147
|
return getNextResponse();
|
|
128
148
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createNextMiddleware.js","names":["compilePathRegex","GTRuntime","libraryDefaultLocale","defaultLocaleCookieName","defaultResetLocaleCookieName","createUnsupportedLocalesWarning","createPathToSharedPathMap","pathnameMatchesRegex","NextResponse","getLocaleFromRequest","getResponse","getSharedPath","replaceDynamicSegments","getLocalizedPath"],"sources":["../../src/middleware-dir/createNextMiddleware.ts"],"sourcesContent":["import { isSameDialect, standardizeLocale } from '@generaltranslation/format';\nimport { GTRuntime } from 'generaltranslation/runtime';\nimport { libraryDefaultLocale } from 'generaltranslation/internal';\nimport { createUnsupportedLocalesWarning } from '../errors/middleware';\nimport { NextRequest, NextResponse } from 'next/server';\nimport {\n defaultLocaleRoutingEnabledCookieName,\n defaultReferrerLocaleCookieName,\n} from '../utils/cookies';\nimport {\n defaultLocaleCookieName,\n defaultResetLocaleCookieName,\n} from '@generaltranslation/react-core/pure';\nimport {\n PathConfig,\n getSharedPath,\n replaceDynamicSegments,\n getLocalizedPath,\n createPathToSharedPathMap,\n getLocaleFromRequest,\n getResponse,\n ResponseConfig,\n} from './utils';\nimport { defaultLocaleHeaderName } from '../utils/headers';\nimport type { CustomMapping } from '@generaltranslation/format/types';\nimport type { HeadersAndCookies } from '../config-dir/props/withGTConfigProps';\nimport { compilePathRegex, pathnameMatchesRegex } from '../utils/pathRegex';\n\nconst NEXT_JS_SOURCE_MAP_PATH = '/__nextjs_source-map';\n\ntype MiddlewareEnvConfig = {\n customMapping?: CustomMapping;\n defaultLocale?: string;\n locales?: string[];\n headersAndCookies?: HeadersAndCookies;\n};\n\n/**\n * Middleware factory to create a Next.js middleware for i18n routing and locale detection.\n *\n * This middleware sets a cookie based on the locale derived from several sources\n * such as the request pathname, referer, or 'Accept-Language' header.\n * If locale routing is enabled, it redirects to the localized pathname and\n * updates the locale cookie.\n *\n * @param {boolean} [config.localeRouting=true] - Flag to enable or disable automatic locale-based routing.\n * @param {boolean} [config.prefixDefaultLocale=false] - Flag to enable or disable prefixing the default locale to the pathname, i.e., /en/about -> /about\n * @param {boolean} [config.ignoreSourceMaps=true] - Flag to enable or disable ignoring source maps\n * @param {PathConfig} [config.pathConfig] - Path configuration for locale routing\n * @returns {function} - A middleware function that processes the request and response.\n */\nexport function createNextMiddleware({\n localeRouting = true,\n prefixDefaultLocale = false,\n ignoreSourceMaps = true,\n pathConfig = {},\n}: {\n localeRouting?: boolean;\n prefixDefaultLocale?: boolean;\n ignoreSourceMaps?: boolean;\n pathConfig?: PathConfig;\n} = {}) {\n const pathRegex = compilePathRegex(\n process.env._GENERALTRANSLATION_PATH_REGEX\n );\n\n // i18n config\n let envParams: MiddlewareEnvConfig | undefined;\n if (process.env._GENERALTRANSLATION_I18N_CONFIG_PARAMS) {\n try {\n envParams = JSON.parse(\n process.env._GENERALTRANSLATION_I18N_CONFIG_PARAMS\n );\n } catch (error) {\n console.error(`gt-next middleware:`, error);\n }\n }\n\n // gt instance\n const gt = new GTRuntime({\n customMapping: envParams?.customMapping,\n });\n\n // using gt services\n const gtServicesEnabled =\n process.env._GENERALTRANSLATION_GT_SERVICES_ENABLED === 'true';\n\n // i18n config\n const defaultLocale: string =\n envParams?.defaultLocale || libraryDefaultLocale;\n const locales: string[] = envParams?.locales || [defaultLocale];\n\n // add canonical locales\n const canonicalLocales = Object.values(envParams?.customMapping || {})\n .filter(\n (locale): locale is { code: string } =>\n typeof locale === 'object' &&\n locale !== null &&\n 'code' in locale &&\n typeof locale.code === 'string' &&\n locale.code.length > 0\n )\n .map((locale) => locale.code);\n locales.push(...canonicalLocales);\n\n // cookies and header names\n const headersAndCookies = envParams?.headersAndCookies || {};\n const localeRoutingEnabledCookieName =\n headersAndCookies?.localeRoutingEnabledCookieName ||\n defaultLocaleRoutingEnabledCookieName;\n const referrerLocaleCookieName =\n headersAndCookies?.referrerLocaleCookieName ||\n defaultReferrerLocaleCookieName;\n const localeCookieName =\n headersAndCookies?.localeCookieName || defaultLocaleCookieName;\n const resetLocaleCookieName =\n headersAndCookies?.resetLocaleCookieName || defaultResetLocaleCookieName;\n const localeHeaderName =\n headersAndCookies?.localeHeaderName || defaultLocaleHeaderName;\n\n if (!gt.isValidLocale(defaultLocale))\n throw new Error(\n `gt-next middleware: defaultLocale \"${defaultLocale}\" is not a valid locale.`\n );\n\n const warningLocales = locales.filter((locale) => !gt.isValidLocale(locale));\n if (warningLocales.length)\n console.warn(createUnsupportedLocalesWarning(warningLocales));\n\n // ---------- PRE-PROCESSING PATHS ---------- //\n\n // Standardize pathConfig paths\n pathConfig = Object.entries(pathConfig).reduce<PathConfig>(\n (acc, [sharedPath, localizedPath]) => {\n if (typeof localizedPath === 'string') {\n acc[sharedPath] = localizedPath;\n } else {\n acc[sharedPath] = Object.entries(localizedPath).reduce<{\n [key: string]: string;\n }>((acc, [locale, localizedPath]) => {\n acc[gtServicesEnabled ? standardizeLocale(locale) : locale] =\n localizedPath;\n return acc;\n }, {});\n }\n return acc;\n },\n {}\n );\n\n // Create the path mapping\n const { pathToSharedPath, defaultLocalePaths } = createPathToSharedPathMap(\n pathConfig,\n prefixDefaultLocale,\n defaultLocale\n );\n\n /**\n * Processes the incoming request to determine the user's locale and sets a locale cookie.\n * Optionally redirects the user based on the locale if locale-based routing is enabled.\n *\n * - Checks if the request URL contains a locale.\n * - Falls back to the referer URL for locale if needed.\n * - If no locale is found in the URL or referer, it checks the 'Accept-Language' header.\n * - Sets a cookie with the detected or default locale.\n * - Redirects to the correct locale route if locale routing is enabled.\n *\n * @param {NextRequest} req - The incoming request object, containing URL and headers.\n * @returns {NextResponse} - The Next.js response, either continuing the request or redirecting to the localized URL.\n */\n function middleware(req: NextRequest) {\n if (!pathnameMatchesRegex(req.nextUrl.pathname, pathRegex)) {\n return NextResponse.next();\n }\n\n // Ignore source maps\n if (\n ignoreSourceMaps &&\n req.nextUrl.pathname.startsWith(NEXT_JS_SOURCE_MAP_PATH)\n ) {\n return NextResponse.next();\n }\n\n // ---------- LOCALE DETECTION ---------- //\n\n const {\n userLocale,\n pathnameLocale,\n unstandardizedPathnameLocale,\n clearResetCookie,\n } = getLocaleFromRequest(\n req,\n defaultLocale,\n locales,\n localeRouting,\n gtServicesEnabled,\n prefixDefaultLocale,\n defaultLocalePaths,\n referrerLocaleCookieName,\n localeCookieName,\n resetLocaleCookieName,\n gt\n );\n\n const headerList = new Headers(req.headers);\n\n const responseConfig: Omit<ResponseConfig, 'type'> = {\n originalUrl: req.nextUrl,\n headerList,\n userLocale,\n clearResetCookie,\n localeRouting,\n localeRoutingEnabledCookieName,\n resetLocaleCookieName,\n localeHeaderName,\n };\n\n const getRewriteResponse = (responsePath: string) =>\n getResponse({ responsePath, type: 'rewrite', ...responseConfig });\n\n const getRedirectResponse = (responsePath: string) =>\n getResponse({ responsePath, type: 'redirect', ...responseConfig });\n\n const getNextResponse = () =>\n getResponse({ type: 'next', ...responseConfig });\n\n if (localeRouting) {\n // ---------- GET PATHS ---------- //\n\n // get pathname\n const { pathname } = req.nextUrl;\n\n // standardize pathname (ie, /tg/welcome -> /fil/welcome), (/blog -> /blog)\n const standardizedPathname =\n pathnameLocale && pathnameLocale !== unstandardizedPathnameLocale\n ? pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n `/${userLocale}`\n )\n : pathname;\n\n // Get the shared path for the unprefixed pathname\n const sharedPath = getSharedPath(\n standardizedPathname,\n pathToSharedPath,\n pathnameLocale\n );\n\n // Get shared path with parameters (/en/dashboard/1/custom), for rewriting localized paths\n const sharedPathWithParameters =\n sharedPath !== undefined\n ? replaceDynamicSegments(\n pathnameLocale\n ? standardizedPathname\n : `/${userLocale}${standardizedPathname}`,\n `/${userLocale}${sharedPath}`\n )\n : undefined;\n\n // Localized path (/en/blog, /fr/fr-about, /fr/dashboard/[id]/custom)\n const localizedPath =\n sharedPath !== undefined\n ? getLocalizedPath(sharedPath, userLocale, pathConfig)\n : undefined;\n\n // Combine localized path with dynamic parameters (/en/blog, /fr/fr-about, /fr/dashboard/1/fr-custom)\n const localizedPathWithParameters =\n localizedPath !== undefined\n ? replaceDynamicSegments(\n pathnameLocale\n ? standardizedPathname\n : `/${userLocale}${standardizedPathname}`,\n localizedPath\n )\n : undefined;\n\n // ---------- ROUTING LOGIC ---------- //\n\n // ----- CASE: no localized path exists ----- //\n\n if (localizedPathWithParameters === undefined) {\n // --- CASE: remove defaultLocale prefix --- //\n\n if (!prefixDefaultLocale && isSameDialect(userLocale, defaultLocale)) {\n if (pathnameLocale) {\n // REDIRECT CASE: used setLocale (/fr/customers -> /customers) (/en/customers -> /customers)\n if (clearResetCookie) {\n return getRedirectResponse(\n pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n ``\n ) || '/'\n );\n }\n } else {\n // REWRITE CASE: no pathnameLocale (/customers -> /en/customers)\n return getRewriteResponse(`/${userLocale}${pathname}`);\n }\n }\n\n // --- CASE: defaultLocale prefix --- //\n // REDIRECT CASE: no pathnameLocale (ie, /customers -> /fr/customers)\n else if (!pathnameLocale) {\n return getRedirectResponse(`/${userLocale}${pathname}`);\n }\n\n // REDIRECT CASE: wrong pathnameLocale (ie, /fr/customers -> /en/customers) (this usually happens after a locale switch)\n if (pathnameLocale && userLocale !== unstandardizedPathnameLocale) {\n return getRedirectResponse(\n pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n `/${userLocale}`\n )\n );\n }\n\n // BASE CASE: has pathnameLocale and it's correct\n return getNextResponse();\n }\n\n // ----- CASE: localized path exists ----- //\n\n if (!prefixDefaultLocale && isSameDialect(userLocale, defaultLocale)) {\n // --- CASE: remove defaultLocale prefix --- //\n\n if (pathnameLocale) {\n // REDIRECT CASE: remove locale prefix when setLocale is used (/en/blog -> /blog) (/fr/fr-about -> /en-about)\n if (clearResetCookie) {\n return getRedirectResponse(\n localizedPathWithParameters.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n ``\n ) || '/'\n );\n }\n } else {\n // REDIRECT CASE: unprefixed pathname is wrong (/about -> /en-about)\n if (\n !pathnameLocale &&\n localizedPathWithParameters !== `/${userLocale}${pathname}`\n ) {\n return getRedirectResponse(\n localizedPathWithParameters.replace(\n new RegExp(`^/${userLocale}`),\n ''\n ) || '/'\n );\n }\n\n // REWRITE CASE: displaying correct path (/blog -> /en/blog)\n return getRewriteResponse(sharedPathWithParameters as string);\n }\n }\n\n // --- CASE: add defaultLocale prefix --- //\n\n // REDIRECT CASE: incorrect pathnameLocale\n if (pathname !== localizedPathWithParameters) {\n return getRedirectResponse(localizedPathWithParameters);\n }\n\n // REWRITE CASE: displaying correct localized path, which is the same as the shared path (/fil/blog => /fil/blog) (/fr/fr-dashboard/1/fr-custom => /fr/dashboard/1/custom)\n if (\n standardizedPathname !== sharedPathWithParameters // no rewrite needed if it's already the shared path\n ) {\n // convert to shared path with dynamic parameters\n return getRewriteResponse(sharedPathWithParameters as string);\n }\n }\n\n // BASE CASE\n return getNextResponse();\n }\n\n return middleware;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createNextMiddleware.js","names":["compilePathRegex","GTRuntime","libraryDefaultLocale","defaultLocaleCookieName","defaultResetLocaleCookieName","createUnsupportedLocalesWarning","createPathToSharedPathMap","pathnameMatchesRegex","NextResponse","getLocaleFromRequest","getResponse","getSharedPath","applyTrailingSlash","replaceDynamicSegments","getLocalizedPath","normalizePathname"],"sources":["../../src/middleware-dir/createNextMiddleware.ts"],"sourcesContent":["import { applyTrailingSlash } from './pathname';\nimport { isSameDialect, standardizeLocale } from '@generaltranslation/format';\nimport { GTRuntime } from 'generaltranslation/runtime';\nimport { libraryDefaultLocale } from 'generaltranslation/internal';\nimport { createUnsupportedLocalesWarning } from '../errors/middleware';\nimport { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\nimport {\n defaultLocaleRoutingEnabledCookieName,\n defaultReferrerLocaleCookieName,\n} from '../utils/cookies';\nimport {\n defaultLocaleCookieName,\n defaultResetLocaleCookieName,\n} from '@generaltranslation/react-core/pure';\nimport {\n normalizePathname,\n getSharedPath,\n replaceDynamicSegments,\n getLocalizedPath,\n createPathToSharedPathMap,\n getLocaleFromRequest,\n getResponse,\n ResponseConfig,\n type PathConfig,\n type PathMatcher,\n} from './utils';\nimport { defaultLocaleHeaderName } from '../utils/headers';\nimport type { CustomMapping } from '@generaltranslation/format/types';\nimport type { HeadersAndCookies } from '../config-dir/props/withGTConfigProps';\nimport { compilePathRegex, pathnameMatchesRegex } from '../utils/pathRegex';\n\nconst NEXT_JS_SOURCE_MAP_PATH = '/__nextjs_source-map';\n\ntype MiddlewareEnvConfig = {\n customMapping?: CustomMapping;\n defaultLocale?: string;\n locales?: string[];\n headersAndCookies?: HeadersAndCookies;\n};\n\nexport type RouteOverrides = Record<string, readonly string[]>;\n\n/**\n * Middleware factory to create a Next.js middleware for i18n routing and locale detection.\n *\n * This middleware sets a cookie based on the locale derived from several sources\n * such as the request pathname, referer, or 'Accept-Language' header.\n * If locale routing is enabled, it redirects to the localized pathname and\n * updates the locale cookie.\n *\n * @param {boolean} [config.localeRouting=true] - Flag to enable or disable automatic locale-based routing.\n * @param {boolean} [config.prefixDefaultLocale=false] - Flag to enable or disable prefixing the default locale to the pathname, i.e., /en/about -> /about\n * @param {boolean} [config.ignoreSourceMaps=true] - Flag to enable or disable ignoring source maps\n * @param {PathConfig} [config.pathConfig] - Path configuration for locale routing\n * @param {RouteOverrides} [config.routeOverrides] - Locale-relative paths to rewrite from /{locale}/{path} to /{locale}/{locale}/{path}\n * @returns {function} - A middleware function that processes the request and response.\n */\nexport function createNextMiddleware({\n localeRouting = true,\n prefixDefaultLocale = false,\n ignoreSourceMaps = true,\n pathConfig = {},\n routeOverrides = {},\n}: {\n localeRouting?: boolean;\n prefixDefaultLocale?: boolean;\n ignoreSourceMaps?: boolean;\n pathConfig?: PathConfig;\n routeOverrides?: RouteOverrides;\n} = {}) {\n const pathRegex = compilePathRegex(\n process.env._GENERALTRANSLATION_PATH_REGEX\n );\n\n // i18n config\n let envParams: MiddlewareEnvConfig | undefined;\n if (process.env._GENERALTRANSLATION_I18N_CONFIG_PARAMS) {\n try {\n envParams = JSON.parse(\n process.env._GENERALTRANSLATION_I18N_CONFIG_PARAMS\n );\n } catch (error) {\n console.error(`gt-next middleware:`, error);\n }\n }\n\n // gt instance\n const gt = new GTRuntime({\n customMapping: envParams?.customMapping,\n });\n\n // using gt services\n const gtServicesEnabled =\n process.env._GENERALTRANSLATION_GT_SERVICES_ENABLED === 'true';\n\n // i18n config\n const defaultLocale: string =\n envParams?.defaultLocale || libraryDefaultLocale;\n const locales: string[] = envParams?.locales || [defaultLocale];\n\n // add canonical locales\n const canonicalLocales = Object.values(envParams?.customMapping || {})\n .filter(\n (locale): locale is { code: string } =>\n typeof locale === 'object' &&\n locale !== null &&\n 'code' in locale &&\n typeof locale.code === 'string' &&\n locale.code.length > 0\n )\n .map((locale) => locale.code);\n locales.push(...canonicalLocales);\n\n // cookies and header names\n const headersAndCookies = envParams?.headersAndCookies || {};\n const localeRoutingEnabledCookieName =\n headersAndCookies?.localeRoutingEnabledCookieName ||\n defaultLocaleRoutingEnabledCookieName;\n const referrerLocaleCookieName =\n headersAndCookies?.referrerLocaleCookieName ||\n defaultReferrerLocaleCookieName;\n const localeCookieName =\n headersAndCookies?.localeCookieName || defaultLocaleCookieName;\n const resetLocaleCookieName =\n headersAndCookies?.resetLocaleCookieName || defaultResetLocaleCookieName;\n const localeHeaderName =\n headersAndCookies?.localeHeaderName || defaultLocaleHeaderName;\n\n if (!gt.isValidLocale(defaultLocale))\n throw new Error(\n `gt-next middleware: defaultLocale \"${defaultLocale}\" is not a valid locale.`\n );\n\n const warningLocales = locales.filter((locale) => !gt.isValidLocale(locale));\n if (warningLocales.length)\n console.warn(createUnsupportedLocalesWarning(warningLocales));\n\n // ---------- PRE-PROCESSING PATHS ---------- //\n\n // Standardize pathConfig paths\n pathConfig = Object.entries(pathConfig).reduce<PathConfig>(\n (acc, [sharedPath, localizedPath]) => {\n if (typeof localizedPath === 'string') {\n acc[sharedPath] = localizedPath;\n } else {\n acc[sharedPath] = Object.entries(localizedPath).reduce<{\n [key: string]: string;\n }>((acc, [locale, localizedPath]) => {\n acc[gtServicesEnabled ? standardizeLocale(locale) : locale] =\n localizedPath;\n return acc;\n }, {});\n }\n return acc;\n },\n {}\n );\n\n // Standardize routeOverrides locales\n routeOverrides = Object.entries(routeOverrides).reduce<RouteOverrides>(\n (acc, [locale, paths]) => {\n acc[gtServicesEnabled ? standardizeLocale(locale) : locale] = paths;\n return acc;\n },\n {}\n );\n\n // Create the route override path mapping\n const routeOverridePathMaps = Object.entries(routeOverrides).reduce<\n Record<string, PathMatcher>\n >((acc, [locale, paths]) => {\n const overridePathConfig = Object.fromEntries(\n paths.map((path) => [path, path])\n );\n acc[locale] = createPathToSharedPathMap(\n overridePathConfig,\n true,\n defaultLocale\n ).pathToSharedPath;\n return acc;\n }, {});\n\n // Create the path mapping\n const { pathToSharedPath, defaultLocalePaths } = createPathToSharedPathMap(\n pathConfig,\n prefixDefaultLocale,\n defaultLocale\n );\n\n /**\n * Processes the incoming request to determine the user's locale and sets a locale cookie.\n * Optionally redirects the user based on the locale if locale-based routing is enabled.\n *\n * - Checks if the request URL contains a locale.\n * - Falls back to the referer URL for locale if needed.\n * - If no locale is found in the URL or referer, it checks the 'Accept-Language' header.\n * - Sets a cookie with the detected or default locale.\n * - Redirects to the correct locale route if locale routing is enabled.\n *\n * @param {NextRequest} req - The incoming request object, containing URL and headers.\n * @returns {NextResponse} - The Next.js response, either continuing the request or redirecting to the localized URL.\n */\n function middleware(req: NextRequest) {\n if (!pathnameMatchesRegex(req.nextUrl.pathname, pathRegex)) {\n return NextResponse.next();\n }\n\n // Ignore source maps\n if (\n ignoreSourceMaps &&\n req.nextUrl.pathname.startsWith(NEXT_JS_SOURCE_MAP_PATH)\n ) {\n return NextResponse.next();\n }\n\n // ---------- LOCALE DETECTION ---------- //\n\n const {\n userLocale,\n pathnameLocale,\n unstandardizedPathnameLocale,\n clearResetCookie,\n } = getLocaleFromRequest(\n req,\n defaultLocale,\n locales,\n localeRouting,\n gtServicesEnabled,\n prefixDefaultLocale,\n defaultLocalePaths,\n referrerLocaleCookieName,\n localeCookieName,\n resetLocaleCookieName,\n gt\n );\n\n const headerList = new Headers(req.headers);\n\n const responseConfig: Omit<ResponseConfig, 'type'> = {\n originalUrl: req.nextUrl,\n headerList,\n userLocale,\n clearResetCookie,\n localeRouting,\n localeRoutingEnabledCookieName,\n resetLocaleCookieName,\n localeHeaderName,\n };\n\n const getRewriteResponse = (responsePath: string) =>\n getResponse({ responsePath, type: 'rewrite', ...responseConfig });\n\n const getRedirectResponse = (responsePath: string) =>\n getResponse({ responsePath, type: 'redirect', ...responseConfig });\n\n const getNextResponse = () =>\n getResponse({ type: 'next', ...responseConfig });\n\n if (localeRouting) {\n // ---------- GET PATHS ---------- //\n\n // get pathname\n const { pathname } = req.nextUrl;\n\n // standardize pathname (ie, /tg/welcome -> /fil/welcome), (/blog -> /blog)\n const standardizedPathname =\n pathnameLocale && pathnameLocale !== unstandardizedPathnameLocale\n ? pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n `/${pathnameLocale}`\n )\n : pathname;\n\n // Get the shared path for the unprefixed pathname\n const sharedPathMatch = getSharedPath(\n standardizedPathname,\n pathToSharedPath,\n pathnameLocale\n );\n const sharedPath = sharedPathMatch?.sharedPath;\n\n // Get shared path with parameters (/en/dashboard/1/custom), for rewriting localized paths\n const sharedPathWithParameters =\n sharedPathMatch !== undefined\n ? applyTrailingSlash(\n standardizedPathname,\n replaceDynamicSegments(\n sharedPathMatch.matchedPathname,\n `/${userLocale}${sharedPath}`,\n sharedPathMatch.pathTemplate\n )\n )\n : undefined;\n\n // Localized path (/en/blog, /fr/fr-about, /fr/dashboard/[id]/custom)\n const localizedPath =\n sharedPath !== undefined\n ? getLocalizedPath(sharedPath, userLocale, pathConfig)\n : undefined;\n\n // Combine localized path with dynamic parameters (/en/blog, /fr/fr-about, /fr/dashboard/1/fr-custom)\n const localizedPathWithParameters =\n localizedPath !== undefined && sharedPathMatch !== undefined\n ? applyTrailingSlash(\n standardizedPathname,\n replaceDynamicSegments(\n sharedPathMatch.matchedPathname,\n localizedPath,\n sharedPathMatch.pathTemplate\n )\n )\n : undefined;\n\n const pagePath =\n (sharedPathWithParameters?.replace(new RegExp(`^/${userLocale}`), '') ??\n (pathnameLocale\n ? standardizedPathname.replace(new RegExp(`^/${userLocale}`), '')\n : standardizedPathname)) ||\n '/';\n const routeOverridePathMap = routeOverridePathMaps[userLocale];\n const routeOverrideMatch = routeOverridePathMap\n ? getSharedPath(pagePath, routeOverridePathMap, undefined)\n : undefined;\n const routeOverridePath =\n routeOverrideMatch !== undefined\n ? applyTrailingSlash(\n standardizedPathname,\n `/${userLocale}/${userLocale}${pagePath === '/' ? '' : pagePath}`\n )\n : undefined;\n\n // ---------- ROUTING LOGIC ---------- //\n\n // ----- CASE: no localized path exists ----- //\n\n if (localizedPathWithParameters === undefined) {\n // --- CASE: remove defaultLocale prefix --- //\n\n if (!prefixDefaultLocale && isSameDialect(userLocale, defaultLocale)) {\n if (pathnameLocale) {\n // REDIRECT CASE: used setLocale (/fr/customers -> /customers) (/en/customers -> /customers)\n if (clearResetCookie) {\n return getRedirectResponse(\n pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n ``\n ) || '/'\n );\n }\n } else {\n // REWRITE CASE: no pathnameLocale (/customers -> /en/customers)\n return getRewriteResponse(\n routeOverridePath || `/${userLocale}${pathname}`\n );\n }\n }\n\n // --- CASE: defaultLocale prefix --- //\n // REDIRECT CASE: no pathnameLocale (ie, /customers -> /fr/customers)\n else if (!pathnameLocale) {\n return getRedirectResponse(`/${userLocale}${pathname}`);\n }\n\n // REDIRECT CASE: wrong pathnameLocale (ie, /fr/customers -> /en/customers) (this usually happens after a locale switch)\n if (pathnameLocale && userLocale !== unstandardizedPathnameLocale) {\n return getRedirectResponse(\n pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n `/${userLocale}`\n )\n );\n }\n\n // BASE CASE: has pathnameLocale and it's correct\n if (routeOverridePath) {\n return getRewriteResponse(routeOverridePath);\n }\n return getNextResponse();\n }\n\n // ----- CASE: localized path exists ----- //\n\n if (!prefixDefaultLocale && isSameDialect(userLocale, defaultLocale)) {\n // --- CASE: remove defaultLocale prefix --- //\n\n if (pathnameLocale) {\n // REDIRECT CASE: remove locale prefix when setLocale is used (/en/blog -> /blog) (/fr/fr-about -> /en-about)\n if (clearResetCookie) {\n return getRedirectResponse(\n localizedPathWithParameters.replace(\n new RegExp(`^/${userLocale}`),\n ``\n ) || '/'\n );\n }\n } else {\n const localizedPublicPath =\n localizedPathWithParameters.replace(\n new RegExp(`^/${userLocale}`),\n ''\n ) || '/';\n\n // REDIRECT CASE: unprefixed pathname is wrong (/about -> /en-about)\n if (\n !pathnameLocale &&\n normalizePathname(localizedPublicPath) !==\n normalizePathname(pathname)\n ) {\n return getRedirectResponse(localizedPublicPath);\n }\n\n // REWRITE CASE: displaying correct path (/blog -> /en/blog)\n return getRewriteResponse(\n routeOverridePath || (sharedPathWithParameters as string)\n );\n }\n }\n\n // --- CASE: add defaultLocale prefix --- //\n\n // REDIRECT CASE: incorrect pathnameLocale\n if (\n normalizePathname(pathname) !==\n normalizePathname(localizedPathWithParameters)\n ) {\n return getRedirectResponse(localizedPathWithParameters);\n }\n\n if (routeOverridePath) {\n return getRewriteResponse(routeOverridePath);\n }\n\n // REWRITE CASE: displaying correct localized path, which is the same as the shared path (/fil/blog => /fil/blog) (/fr/fr-dashboard/1/fr-custom => /fr/dashboard/1/custom)\n // Next.js route identity preserves Unicode spelling, even when lookup\n // considers the public alias and shared path canonically equivalent.\n const rewriteUrl = new URL(\n sharedPathWithParameters as string,\n req.nextUrl\n );\n if (\n req.nextUrl.pathname !== rewriteUrl.pathname // no rewrite needed if it's already the shared path\n ) {\n // convert to shared path with dynamic parameters\n return getRewriteResponse(sharedPathWithParameters as string);\n }\n }\n\n // BASE CASE\n return getNextResponse();\n }\n\n return middleware;\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import "../utils/headers.mjs";
|
|
2
2
|
import "../utils/cookies.mjs";
|
|
3
3
|
import { compilePathRegex, pathnameMatchesRegex } from "../utils/pathRegex.mjs";
|
|
4
|
+
import { applyTrailingSlash, normalizePathname } from "./pathname.mjs";
|
|
4
5
|
import { createUnsupportedLocalesWarning } from "../errors/middleware.mjs";
|
|
5
|
-
import { createPathToSharedPathMap
|
|
6
|
+
import { createPathToSharedPathMap } from "./createPathMatcher.mjs";
|
|
7
|
+
import { getSharedPath } from "./matchPath.mjs";
|
|
8
|
+
import { getLocaleFromRequest, getLocalizedPath, getResponse, replaceDynamicSegments } from "./utils.mjs";
|
|
6
9
|
import { libraryDefaultLocale } from "generaltranslation/internal";
|
|
7
10
|
import { defaultLocaleCookieName, defaultResetLocaleCookieName } from "@generaltranslation/react-core/pure";
|
|
8
11
|
import { isSameDialect, standardizeLocale } from "@generaltranslation/format";
|
|
@@ -22,9 +25,10 @@ const NEXT_JS_SOURCE_MAP_PATH = "/__nextjs_source-map";
|
|
|
22
25
|
* @param {boolean} [config.prefixDefaultLocale=false] - Flag to enable or disable prefixing the default locale to the pathname, i.e., /en/about -> /about
|
|
23
26
|
* @param {boolean} [config.ignoreSourceMaps=true] - Flag to enable or disable ignoring source maps
|
|
24
27
|
* @param {PathConfig} [config.pathConfig] - Path configuration for locale routing
|
|
28
|
+
* @param {RouteOverrides} [config.routeOverrides] - Locale-relative paths to rewrite from /{locale}/{path} to /{locale}/{locale}/{path}
|
|
25
29
|
* @returns {function} - A middleware function that processes the request and response.
|
|
26
30
|
*/
|
|
27
|
-
function createNextMiddleware({ localeRouting = true, prefixDefaultLocale = false, ignoreSourceMaps = true, pathConfig = {} } = {}) {
|
|
31
|
+
function createNextMiddleware({ localeRouting = true, prefixDefaultLocale = false, ignoreSourceMaps = true, pathConfig = {}, routeOverrides = {} } = {}) {
|
|
28
32
|
const pathRegex = compilePathRegex(process.env._GENERALTRANSLATION_PATH_REGEX);
|
|
29
33
|
let envParams;
|
|
30
34
|
if (process.env._GENERALTRANSLATION_I18N_CONFIG_PARAMS) try {
|
|
@@ -55,6 +59,14 @@ function createNextMiddleware({ localeRouting = true, prefixDefaultLocale = fals
|
|
|
55
59
|
}, {});
|
|
56
60
|
return acc;
|
|
57
61
|
}, {});
|
|
62
|
+
routeOverrides = Object.entries(routeOverrides).reduce((acc, [locale, paths]) => {
|
|
63
|
+
acc[gtServicesEnabled ? standardizeLocale(locale) : locale] = paths;
|
|
64
|
+
return acc;
|
|
65
|
+
}, {});
|
|
66
|
+
const routeOverridePathMaps = Object.entries(routeOverrides).reduce((acc, [locale, paths]) => {
|
|
67
|
+
acc[locale] = createPathToSharedPathMap(Object.fromEntries(paths.map((path) => [path, path])), true, defaultLocale).pathToSharedPath;
|
|
68
|
+
return acc;
|
|
69
|
+
}, {});
|
|
58
70
|
const { pathToSharedPath, defaultLocalePaths } = createPathToSharedPathMap(pathConfig, prefixDefaultLocale, defaultLocale);
|
|
59
71
|
/**
|
|
60
72
|
* Processes the incoming request to determine the user's locale and sets a locale cookie.
|
|
@@ -100,27 +112,35 @@ function createNextMiddleware({ localeRouting = true, prefixDefaultLocale = fals
|
|
|
100
112
|
});
|
|
101
113
|
if (localeRouting) {
|
|
102
114
|
const { pathname } = req.nextUrl;
|
|
103
|
-
const standardizedPathname = pathnameLocale && pathnameLocale !== unstandardizedPathnameLocale ? pathname.replace(new RegExp(`^/${unstandardizedPathnameLocale}`), `/${
|
|
104
|
-
const
|
|
105
|
-
const
|
|
115
|
+
const standardizedPathname = pathnameLocale && pathnameLocale !== unstandardizedPathnameLocale ? pathname.replace(new RegExp(`^/${unstandardizedPathnameLocale}`), `/${pathnameLocale}`) : pathname;
|
|
116
|
+
const sharedPathMatch = getSharedPath(standardizedPathname, pathToSharedPath, pathnameLocale);
|
|
117
|
+
const sharedPath = sharedPathMatch?.sharedPath;
|
|
118
|
+
const sharedPathWithParameters = sharedPathMatch !== void 0 ? applyTrailingSlash(standardizedPathname, replaceDynamicSegments(sharedPathMatch.matchedPathname, `/${userLocale}${sharedPath}`, sharedPathMatch.pathTemplate)) : void 0;
|
|
106
119
|
const localizedPath = sharedPath !== void 0 ? getLocalizedPath(sharedPath, userLocale, pathConfig) : void 0;
|
|
107
|
-
const localizedPathWithParameters = localizedPath !== void 0
|
|
120
|
+
const localizedPathWithParameters = localizedPath !== void 0 && sharedPathMatch !== void 0 ? applyTrailingSlash(standardizedPathname, replaceDynamicSegments(sharedPathMatch.matchedPathname, localizedPath, sharedPathMatch.pathTemplate)) : void 0;
|
|
121
|
+
const pagePath = (sharedPathWithParameters?.replace(new RegExp(`^/${userLocale}`), "") ?? (pathnameLocale ? standardizedPathname.replace(new RegExp(`^/${userLocale}`), "") : standardizedPathname)) || "/";
|
|
122
|
+
const routeOverridePathMap = routeOverridePathMaps[userLocale];
|
|
123
|
+
const routeOverridePath = (routeOverridePathMap ? getSharedPath(pagePath, routeOverridePathMap, void 0) : void 0) !== void 0 ? applyTrailingSlash(standardizedPathname, `/${userLocale}/${userLocale}${pagePath === "/" ? "" : pagePath}`) : void 0;
|
|
108
124
|
if (localizedPathWithParameters === void 0) {
|
|
109
125
|
if (!prefixDefaultLocale && isSameDialect(userLocale, defaultLocale)) if (pathnameLocale) {
|
|
110
126
|
if (clearResetCookie) return getRedirectResponse(pathname.replace(new RegExp(`^/${unstandardizedPathnameLocale}`), ``) || "/");
|
|
111
|
-
} else return getRewriteResponse(`/${userLocale}${pathname}`);
|
|
127
|
+
} else return getRewriteResponse(routeOverridePath || `/${userLocale}${pathname}`);
|
|
112
128
|
else if (!pathnameLocale) return getRedirectResponse(`/${userLocale}${pathname}`);
|
|
113
129
|
if (pathnameLocale && userLocale !== unstandardizedPathnameLocale) return getRedirectResponse(pathname.replace(new RegExp(`^/${unstandardizedPathnameLocale}`), `/${userLocale}`));
|
|
130
|
+
if (routeOverridePath) return getRewriteResponse(routeOverridePath);
|
|
114
131
|
return getNextResponse();
|
|
115
132
|
}
|
|
116
133
|
if (!prefixDefaultLocale && isSameDialect(userLocale, defaultLocale)) if (pathnameLocale) {
|
|
117
|
-
if (clearResetCookie) return getRedirectResponse(localizedPathWithParameters.replace(new RegExp(`^/${
|
|
134
|
+
if (clearResetCookie) return getRedirectResponse(localizedPathWithParameters.replace(new RegExp(`^/${userLocale}`), ``) || "/");
|
|
118
135
|
} else {
|
|
119
|
-
|
|
120
|
-
return
|
|
136
|
+
const localizedPublicPath = localizedPathWithParameters.replace(new RegExp(`^/${userLocale}`), "") || "/";
|
|
137
|
+
if (!pathnameLocale && normalizePathname(localizedPublicPath) !== normalizePathname(pathname)) return getRedirectResponse(localizedPublicPath);
|
|
138
|
+
return getRewriteResponse(routeOverridePath || sharedPathWithParameters);
|
|
121
139
|
}
|
|
122
|
-
if (pathname !== localizedPathWithParameters) return getRedirectResponse(localizedPathWithParameters);
|
|
123
|
-
if (
|
|
140
|
+
if (normalizePathname(pathname) !== normalizePathname(localizedPathWithParameters)) return getRedirectResponse(localizedPathWithParameters);
|
|
141
|
+
if (routeOverridePath) return getRewriteResponse(routeOverridePath);
|
|
142
|
+
const rewriteUrl = new URL(sharedPathWithParameters, req.nextUrl);
|
|
143
|
+
if (req.nextUrl.pathname !== rewriteUrl.pathname) return getRewriteResponse(sharedPathWithParameters);
|
|
124
144
|
}
|
|
125
145
|
return getNextResponse();
|
|
126
146
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createNextMiddleware.mjs","names":[],"sources":["../../src/middleware-dir/createNextMiddleware.ts"],"sourcesContent":["import { isSameDialect, standardizeLocale } from '@generaltranslation/format';\nimport { GTRuntime } from 'generaltranslation/runtime';\nimport { libraryDefaultLocale } from 'generaltranslation/internal';\nimport { createUnsupportedLocalesWarning } from '../errors/middleware';\nimport { NextRequest, NextResponse } from 'next/server';\nimport {\n defaultLocaleRoutingEnabledCookieName,\n defaultReferrerLocaleCookieName,\n} from '../utils/cookies';\nimport {\n defaultLocaleCookieName,\n defaultResetLocaleCookieName,\n} from '@generaltranslation/react-core/pure';\nimport {\n PathConfig,\n getSharedPath,\n replaceDynamicSegments,\n getLocalizedPath,\n createPathToSharedPathMap,\n getLocaleFromRequest,\n getResponse,\n ResponseConfig,\n} from './utils';\nimport { defaultLocaleHeaderName } from '../utils/headers';\nimport type { CustomMapping } from '@generaltranslation/format/types';\nimport type { HeadersAndCookies } from '../config-dir/props/withGTConfigProps';\nimport { compilePathRegex, pathnameMatchesRegex } from '../utils/pathRegex';\n\nconst NEXT_JS_SOURCE_MAP_PATH = '/__nextjs_source-map';\n\ntype MiddlewareEnvConfig = {\n customMapping?: CustomMapping;\n defaultLocale?: string;\n locales?: string[];\n headersAndCookies?: HeadersAndCookies;\n};\n\n/**\n * Middleware factory to create a Next.js middleware for i18n routing and locale detection.\n *\n * This middleware sets a cookie based on the locale derived from several sources\n * such as the request pathname, referer, or 'Accept-Language' header.\n * If locale routing is enabled, it redirects to the localized pathname and\n * updates the locale cookie.\n *\n * @param {boolean} [config.localeRouting=true] - Flag to enable or disable automatic locale-based routing.\n * @param {boolean} [config.prefixDefaultLocale=false] - Flag to enable or disable prefixing the default locale to the pathname, i.e., /en/about -> /about\n * @param {boolean} [config.ignoreSourceMaps=true] - Flag to enable or disable ignoring source maps\n * @param {PathConfig} [config.pathConfig] - Path configuration for locale routing\n * @returns {function} - A middleware function that processes the request and response.\n */\nexport function createNextMiddleware({\n localeRouting = true,\n prefixDefaultLocale = false,\n ignoreSourceMaps = true,\n pathConfig = {},\n}: {\n localeRouting?: boolean;\n prefixDefaultLocale?: boolean;\n ignoreSourceMaps?: boolean;\n pathConfig?: PathConfig;\n} = {}) {\n const pathRegex = compilePathRegex(\n process.env._GENERALTRANSLATION_PATH_REGEX\n );\n\n // i18n config\n let envParams: MiddlewareEnvConfig | undefined;\n if (process.env._GENERALTRANSLATION_I18N_CONFIG_PARAMS) {\n try {\n envParams = JSON.parse(\n process.env._GENERALTRANSLATION_I18N_CONFIG_PARAMS\n );\n } catch (error) {\n console.error(`gt-next middleware:`, error);\n }\n }\n\n // gt instance\n const gt = new GTRuntime({\n customMapping: envParams?.customMapping,\n });\n\n // using gt services\n const gtServicesEnabled =\n process.env._GENERALTRANSLATION_GT_SERVICES_ENABLED === 'true';\n\n // i18n config\n const defaultLocale: string =\n envParams?.defaultLocale || libraryDefaultLocale;\n const locales: string[] = envParams?.locales || [defaultLocale];\n\n // add canonical locales\n const canonicalLocales = Object.values(envParams?.customMapping || {})\n .filter(\n (locale): locale is { code: string } =>\n typeof locale === 'object' &&\n locale !== null &&\n 'code' in locale &&\n typeof locale.code === 'string' &&\n locale.code.length > 0\n )\n .map((locale) => locale.code);\n locales.push(...canonicalLocales);\n\n // cookies and header names\n const headersAndCookies = envParams?.headersAndCookies || {};\n const localeRoutingEnabledCookieName =\n headersAndCookies?.localeRoutingEnabledCookieName ||\n defaultLocaleRoutingEnabledCookieName;\n const referrerLocaleCookieName =\n headersAndCookies?.referrerLocaleCookieName ||\n defaultReferrerLocaleCookieName;\n const localeCookieName =\n headersAndCookies?.localeCookieName || defaultLocaleCookieName;\n const resetLocaleCookieName =\n headersAndCookies?.resetLocaleCookieName || defaultResetLocaleCookieName;\n const localeHeaderName =\n headersAndCookies?.localeHeaderName || defaultLocaleHeaderName;\n\n if (!gt.isValidLocale(defaultLocale))\n throw new Error(\n `gt-next middleware: defaultLocale \"${defaultLocale}\" is not a valid locale.`\n );\n\n const warningLocales = locales.filter((locale) => !gt.isValidLocale(locale));\n if (warningLocales.length)\n console.warn(createUnsupportedLocalesWarning(warningLocales));\n\n // ---------- PRE-PROCESSING PATHS ---------- //\n\n // Standardize pathConfig paths\n pathConfig = Object.entries(pathConfig).reduce<PathConfig>(\n (acc, [sharedPath, localizedPath]) => {\n if (typeof localizedPath === 'string') {\n acc[sharedPath] = localizedPath;\n } else {\n acc[sharedPath] = Object.entries(localizedPath).reduce<{\n [key: string]: string;\n }>((acc, [locale, localizedPath]) => {\n acc[gtServicesEnabled ? standardizeLocale(locale) : locale] =\n localizedPath;\n return acc;\n }, {});\n }\n return acc;\n },\n {}\n );\n\n // Create the path mapping\n const { pathToSharedPath, defaultLocalePaths } = createPathToSharedPathMap(\n pathConfig,\n prefixDefaultLocale,\n defaultLocale\n );\n\n /**\n * Processes the incoming request to determine the user's locale and sets a locale cookie.\n * Optionally redirects the user based on the locale if locale-based routing is enabled.\n *\n * - Checks if the request URL contains a locale.\n * - Falls back to the referer URL for locale if needed.\n * - If no locale is found in the URL or referer, it checks the 'Accept-Language' header.\n * - Sets a cookie with the detected or default locale.\n * - Redirects to the correct locale route if locale routing is enabled.\n *\n * @param {NextRequest} req - The incoming request object, containing URL and headers.\n * @returns {NextResponse} - The Next.js response, either continuing the request or redirecting to the localized URL.\n */\n function middleware(req: NextRequest) {\n if (!pathnameMatchesRegex(req.nextUrl.pathname, pathRegex)) {\n return NextResponse.next();\n }\n\n // Ignore source maps\n if (\n ignoreSourceMaps &&\n req.nextUrl.pathname.startsWith(NEXT_JS_SOURCE_MAP_PATH)\n ) {\n return NextResponse.next();\n }\n\n // ---------- LOCALE DETECTION ---------- //\n\n const {\n userLocale,\n pathnameLocale,\n unstandardizedPathnameLocale,\n clearResetCookie,\n } = getLocaleFromRequest(\n req,\n defaultLocale,\n locales,\n localeRouting,\n gtServicesEnabled,\n prefixDefaultLocale,\n defaultLocalePaths,\n referrerLocaleCookieName,\n localeCookieName,\n resetLocaleCookieName,\n gt\n );\n\n const headerList = new Headers(req.headers);\n\n const responseConfig: Omit<ResponseConfig, 'type'> = {\n originalUrl: req.nextUrl,\n headerList,\n userLocale,\n clearResetCookie,\n localeRouting,\n localeRoutingEnabledCookieName,\n resetLocaleCookieName,\n localeHeaderName,\n };\n\n const getRewriteResponse = (responsePath: string) =>\n getResponse({ responsePath, type: 'rewrite', ...responseConfig });\n\n const getRedirectResponse = (responsePath: string) =>\n getResponse({ responsePath, type: 'redirect', ...responseConfig });\n\n const getNextResponse = () =>\n getResponse({ type: 'next', ...responseConfig });\n\n if (localeRouting) {\n // ---------- GET PATHS ---------- //\n\n // get pathname\n const { pathname } = req.nextUrl;\n\n // standardize pathname (ie, /tg/welcome -> /fil/welcome), (/blog -> /blog)\n const standardizedPathname =\n pathnameLocale && pathnameLocale !== unstandardizedPathnameLocale\n ? pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n `/${userLocale}`\n )\n : pathname;\n\n // Get the shared path for the unprefixed pathname\n const sharedPath = getSharedPath(\n standardizedPathname,\n pathToSharedPath,\n pathnameLocale\n );\n\n // Get shared path with parameters (/en/dashboard/1/custom), for rewriting localized paths\n const sharedPathWithParameters =\n sharedPath !== undefined\n ? replaceDynamicSegments(\n pathnameLocale\n ? standardizedPathname\n : `/${userLocale}${standardizedPathname}`,\n `/${userLocale}${sharedPath}`\n )\n : undefined;\n\n // Localized path (/en/blog, /fr/fr-about, /fr/dashboard/[id]/custom)\n const localizedPath =\n sharedPath !== undefined\n ? getLocalizedPath(sharedPath, userLocale, pathConfig)\n : undefined;\n\n // Combine localized path with dynamic parameters (/en/blog, /fr/fr-about, /fr/dashboard/1/fr-custom)\n const localizedPathWithParameters =\n localizedPath !== undefined\n ? replaceDynamicSegments(\n pathnameLocale\n ? standardizedPathname\n : `/${userLocale}${standardizedPathname}`,\n localizedPath\n )\n : undefined;\n\n // ---------- ROUTING LOGIC ---------- //\n\n // ----- CASE: no localized path exists ----- //\n\n if (localizedPathWithParameters === undefined) {\n // --- CASE: remove defaultLocale prefix --- //\n\n if (!prefixDefaultLocale && isSameDialect(userLocale, defaultLocale)) {\n if (pathnameLocale) {\n // REDIRECT CASE: used setLocale (/fr/customers -> /customers) (/en/customers -> /customers)\n if (clearResetCookie) {\n return getRedirectResponse(\n pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n ``\n ) || '/'\n );\n }\n } else {\n // REWRITE CASE: no pathnameLocale (/customers -> /en/customers)\n return getRewriteResponse(`/${userLocale}${pathname}`);\n }\n }\n\n // --- CASE: defaultLocale prefix --- //\n // REDIRECT CASE: no pathnameLocale (ie, /customers -> /fr/customers)\n else if (!pathnameLocale) {\n return getRedirectResponse(`/${userLocale}${pathname}`);\n }\n\n // REDIRECT CASE: wrong pathnameLocale (ie, /fr/customers -> /en/customers) (this usually happens after a locale switch)\n if (pathnameLocale && userLocale !== unstandardizedPathnameLocale) {\n return getRedirectResponse(\n pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n `/${userLocale}`\n )\n );\n }\n\n // BASE CASE: has pathnameLocale and it's correct\n return getNextResponse();\n }\n\n // ----- CASE: localized path exists ----- //\n\n if (!prefixDefaultLocale && isSameDialect(userLocale, defaultLocale)) {\n // --- CASE: remove defaultLocale prefix --- //\n\n if (pathnameLocale) {\n // REDIRECT CASE: remove locale prefix when setLocale is used (/en/blog -> /blog) (/fr/fr-about -> /en-about)\n if (clearResetCookie) {\n return getRedirectResponse(\n localizedPathWithParameters.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n ``\n ) || '/'\n );\n }\n } else {\n // REDIRECT CASE: unprefixed pathname is wrong (/about -> /en-about)\n if (\n !pathnameLocale &&\n localizedPathWithParameters !== `/${userLocale}${pathname}`\n ) {\n return getRedirectResponse(\n localizedPathWithParameters.replace(\n new RegExp(`^/${userLocale}`),\n ''\n ) || '/'\n );\n }\n\n // REWRITE CASE: displaying correct path (/blog -> /en/blog)\n return getRewriteResponse(sharedPathWithParameters as string);\n }\n }\n\n // --- CASE: add defaultLocale prefix --- //\n\n // REDIRECT CASE: incorrect pathnameLocale\n if (pathname !== localizedPathWithParameters) {\n return getRedirectResponse(localizedPathWithParameters);\n }\n\n // REWRITE CASE: displaying correct localized path, which is the same as the shared path (/fil/blog => /fil/blog) (/fr/fr-dashboard/1/fr-custom => /fr/dashboard/1/custom)\n if (\n standardizedPathname !== sharedPathWithParameters // no rewrite needed if it's already the shared path\n ) {\n // convert to shared path with dynamic parameters\n return getRewriteResponse(sharedPathWithParameters as string);\n }\n }\n\n // BASE CASE\n return getNextResponse();\n }\n\n return middleware;\n}\n"],"mappings":";;;;;;;;;;;AA4BA,MAAM,0BAA0B;;;;;;;;;;;;;;;AAuBhC,SAAgB,qBAAqB,EACnC,gBAAgB,MAChB,sBAAsB,OACtB,mBAAmB,MACnB,aAAa,EAAE,KAMb,EAAE,EAAE;CACN,MAAM,YAAY,iBAChB,QAAQ,IAAI,+BACb;CAGD,IAAI;AACJ,KAAI,QAAQ,IAAI,uCACd,KAAI;AACF,cAAY,KAAK,MACf,QAAQ,IAAI,uCACb;UACM,OAAO;AACd,UAAQ,MAAM,uBAAuB,MAAM;;CAK/C,MAAM,KAAK,IAAI,UAAU,EACvB,eAAe,WAAW,eAC3B,CAAC;CAGF,MAAM,oBACJ,QAAQ,IAAI,4CAA4C;CAG1D,MAAM,gBACJ,WAAW,iBAAiB;CAC9B,MAAM,UAAoB,WAAW,WAAW,CAAC,cAAc;CAG/D,MAAM,mBAAmB,OAAO,OAAO,WAAW,iBAAiB,EAAE,CAAC,CACnE,QACE,WACC,OAAO,WAAW,YAClB,WAAW,QACX,UAAU,UACV,OAAO,OAAO,SAAS,YACvB,OAAO,KAAK,SAAS,EACxB,CACA,KAAK,WAAW,OAAO,KAAK;AAC/B,SAAQ,KAAK,GAAG,iBAAiB;CAGjC,MAAM,oBAAoB,WAAW,qBAAqB,EAAE;CAC5D,MAAM,iCACJ,mBAAmB,kCAAA;CAErB,MAAM,2BACJ,mBAAmB,4BAAA;CAErB,MAAM,mBACJ,mBAAmB,oBAAoB;CACzC,MAAM,wBACJ,mBAAmB,yBAAyB;CAC9C,MAAM,mBACJ,mBAAmB,oBAAA;AAErB,KAAI,CAAC,GAAG,cAAc,cAAc,CAClC,OAAM,IAAI,MACR,sCAAsC,cAAc,0BACrD;CAEH,MAAM,iBAAiB,QAAQ,QAAQ,WAAW,CAAC,GAAG,cAAc,OAAO,CAAC;AAC5E,KAAI,eAAe,OACjB,SAAQ,KAAK,gCAAgC,eAAe,CAAC;AAK/D,cAAa,OAAO,QAAQ,WAAW,CAAC,QACrC,KAAK,CAAC,YAAY,mBAAmB;AACpC,MAAI,OAAO,kBAAkB,SAC3B,KAAI,cAAc;MAElB,KAAI,cAAc,OAAO,QAAQ,cAAc,CAAC,QAE5C,KAAK,CAAC,QAAQ,mBAAmB;AACnC,OAAI,oBAAoB,kBAAkB,OAAO,GAAG,UAClD;AACF,UAAO;KACN,EAAE,CAAC;AAER,SAAO;IAET,EAAE,CACH;CAGD,MAAM,EAAE,kBAAkB,uBAAuB,0BAC/C,YACA,qBACA,cACD;;;;;;;;;;;;;;CAeD,SAAS,WAAW,KAAkB;AACpC,MAAI,CAAC,qBAAqB,IAAI,QAAQ,UAAU,UAAU,CACxD,QAAO,aAAa,MAAM;AAI5B,MACE,oBACA,IAAI,QAAQ,SAAS,WAAW,wBAAwB,CAExD,QAAO,aAAa,MAAM;EAK5B,MAAM,EACJ,YACA,gBACA,8BACA,qBACE,qBACF,KACA,eACA,SACA,eACA,mBACA,qBACA,oBACA,0BACA,kBACA,uBACA,GACD;EAED,MAAM,aAAa,IAAI,QAAQ,IAAI,QAAQ;EAE3C,MAAM,iBAA+C;GACnD,aAAa,IAAI;GACjB;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EAED,MAAM,sBAAsB,iBAC1B,YAAY;GAAE;GAAc,MAAM;GAAW,GAAG;GAAgB,CAAC;EAEnE,MAAM,uBAAuB,iBAC3B,YAAY;GAAE;GAAc,MAAM;GAAY,GAAG;GAAgB,CAAC;EAEpE,MAAM,wBACJ,YAAY;GAAE,MAAM;GAAQ,GAAG;GAAgB,CAAC;AAElD,MAAI,eAAe;GAIjB,MAAM,EAAE,aAAa,IAAI;GAGzB,MAAM,uBACJ,kBAAkB,mBAAmB,+BACjC,SAAS,QACP,IAAI,OAAO,KAAK,+BAA+B,EAC/C,IAAI,aACL,GACD;GAGN,MAAM,aAAa,cACjB,sBACA,kBACA,eACD;GAGD,MAAM,2BACJ,eAAe,KAAA,IACX,uBACE,iBACI,uBACA,IAAI,aAAa,wBACrB,IAAI,aAAa,aAClB,GACD,KAAA;GAGN,MAAM,gBACJ,eAAe,KAAA,IACX,iBAAiB,YAAY,YAAY,WAAW,GACpD,KAAA;GAGN,MAAM,8BACJ,kBAAkB,KAAA,IACd,uBACE,iBACI,uBACA,IAAI,aAAa,wBACrB,cACD,GACD,KAAA;AAMN,OAAI,gCAAgC,KAAA,GAAW;AAG7C,QAAI,CAAC,uBAAuB,cAAc,YAAY,cAAc,CAClE,KAAI;SAEE,iBACF,QAAO,oBACL,SAAS,QACP,IAAI,OAAO,KAAK,+BAA+B,EAC/C,GACD,IAAI,IACN;UAIH,QAAO,mBAAmB,IAAI,aAAa,WAAW;aAMjD,CAAC,eACR,QAAO,oBAAoB,IAAI,aAAa,WAAW;AAIzD,QAAI,kBAAkB,eAAe,6BACnC,QAAO,oBACL,SAAS,QACP,IAAI,OAAO,KAAK,+BAA+B,EAC/C,IAAI,aACL,CACF;AAIH,WAAO,iBAAiB;;AAK1B,OAAI,CAAC,uBAAuB,cAAc,YAAY,cAAc,CAGlE,KAAI;QAEE,iBACF,QAAO,oBACL,4BAA4B,QAC1B,IAAI,OAAO,KAAK,+BAA+B,EAC/C,GACD,IAAI,IACN;UAEE;AAEL,QACE,CAAC,kBACD,gCAAgC,IAAI,aAAa,WAEjD,QAAO,oBACL,4BAA4B,QAC1B,IAAI,OAAO,KAAK,aAAa,EAC7B,GACD,IAAI,IACN;AAIH,WAAO,mBAAmB,yBAAmC;;AAOjE,OAAI,aAAa,4BACf,QAAO,oBAAoB,4BAA4B;AAIzD,OACE,yBAAyB,yBAGzB,QAAO,mBAAmB,yBAAmC;;AAKjE,SAAO,iBAAiB;;AAG1B,QAAO"}
|
|
1
|
+
{"version":3,"file":"createNextMiddleware.mjs","names":[],"sources":["../../src/middleware-dir/createNextMiddleware.ts"],"sourcesContent":["import { applyTrailingSlash } from './pathname';\nimport { isSameDialect, standardizeLocale } from '@generaltranslation/format';\nimport { GTRuntime } from 'generaltranslation/runtime';\nimport { libraryDefaultLocale } from 'generaltranslation/internal';\nimport { createUnsupportedLocalesWarning } from '../errors/middleware';\nimport { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\nimport {\n defaultLocaleRoutingEnabledCookieName,\n defaultReferrerLocaleCookieName,\n} from '../utils/cookies';\nimport {\n defaultLocaleCookieName,\n defaultResetLocaleCookieName,\n} from '@generaltranslation/react-core/pure';\nimport {\n normalizePathname,\n getSharedPath,\n replaceDynamicSegments,\n getLocalizedPath,\n createPathToSharedPathMap,\n getLocaleFromRequest,\n getResponse,\n ResponseConfig,\n type PathConfig,\n type PathMatcher,\n} from './utils';\nimport { defaultLocaleHeaderName } from '../utils/headers';\nimport type { CustomMapping } from '@generaltranslation/format/types';\nimport type { HeadersAndCookies } from '../config-dir/props/withGTConfigProps';\nimport { compilePathRegex, pathnameMatchesRegex } from '../utils/pathRegex';\n\nconst NEXT_JS_SOURCE_MAP_PATH = '/__nextjs_source-map';\n\ntype MiddlewareEnvConfig = {\n customMapping?: CustomMapping;\n defaultLocale?: string;\n locales?: string[];\n headersAndCookies?: HeadersAndCookies;\n};\n\nexport type RouteOverrides = Record<string, readonly string[]>;\n\n/**\n * Middleware factory to create a Next.js middleware for i18n routing and locale detection.\n *\n * This middleware sets a cookie based on the locale derived from several sources\n * such as the request pathname, referer, or 'Accept-Language' header.\n * If locale routing is enabled, it redirects to the localized pathname and\n * updates the locale cookie.\n *\n * @param {boolean} [config.localeRouting=true] - Flag to enable or disable automatic locale-based routing.\n * @param {boolean} [config.prefixDefaultLocale=false] - Flag to enable or disable prefixing the default locale to the pathname, i.e., /en/about -> /about\n * @param {boolean} [config.ignoreSourceMaps=true] - Flag to enable or disable ignoring source maps\n * @param {PathConfig} [config.pathConfig] - Path configuration for locale routing\n * @param {RouteOverrides} [config.routeOverrides] - Locale-relative paths to rewrite from /{locale}/{path} to /{locale}/{locale}/{path}\n * @returns {function} - A middleware function that processes the request and response.\n */\nexport function createNextMiddleware({\n localeRouting = true,\n prefixDefaultLocale = false,\n ignoreSourceMaps = true,\n pathConfig = {},\n routeOverrides = {},\n}: {\n localeRouting?: boolean;\n prefixDefaultLocale?: boolean;\n ignoreSourceMaps?: boolean;\n pathConfig?: PathConfig;\n routeOverrides?: RouteOverrides;\n} = {}) {\n const pathRegex = compilePathRegex(\n process.env._GENERALTRANSLATION_PATH_REGEX\n );\n\n // i18n config\n let envParams: MiddlewareEnvConfig | undefined;\n if (process.env._GENERALTRANSLATION_I18N_CONFIG_PARAMS) {\n try {\n envParams = JSON.parse(\n process.env._GENERALTRANSLATION_I18N_CONFIG_PARAMS\n );\n } catch (error) {\n console.error(`gt-next middleware:`, error);\n }\n }\n\n // gt instance\n const gt = new GTRuntime({\n customMapping: envParams?.customMapping,\n });\n\n // using gt services\n const gtServicesEnabled =\n process.env._GENERALTRANSLATION_GT_SERVICES_ENABLED === 'true';\n\n // i18n config\n const defaultLocale: string =\n envParams?.defaultLocale || libraryDefaultLocale;\n const locales: string[] = envParams?.locales || [defaultLocale];\n\n // add canonical locales\n const canonicalLocales = Object.values(envParams?.customMapping || {})\n .filter(\n (locale): locale is { code: string } =>\n typeof locale === 'object' &&\n locale !== null &&\n 'code' in locale &&\n typeof locale.code === 'string' &&\n locale.code.length > 0\n )\n .map((locale) => locale.code);\n locales.push(...canonicalLocales);\n\n // cookies and header names\n const headersAndCookies = envParams?.headersAndCookies || {};\n const localeRoutingEnabledCookieName =\n headersAndCookies?.localeRoutingEnabledCookieName ||\n defaultLocaleRoutingEnabledCookieName;\n const referrerLocaleCookieName =\n headersAndCookies?.referrerLocaleCookieName ||\n defaultReferrerLocaleCookieName;\n const localeCookieName =\n headersAndCookies?.localeCookieName || defaultLocaleCookieName;\n const resetLocaleCookieName =\n headersAndCookies?.resetLocaleCookieName || defaultResetLocaleCookieName;\n const localeHeaderName =\n headersAndCookies?.localeHeaderName || defaultLocaleHeaderName;\n\n if (!gt.isValidLocale(defaultLocale))\n throw new Error(\n `gt-next middleware: defaultLocale \"${defaultLocale}\" is not a valid locale.`\n );\n\n const warningLocales = locales.filter((locale) => !gt.isValidLocale(locale));\n if (warningLocales.length)\n console.warn(createUnsupportedLocalesWarning(warningLocales));\n\n // ---------- PRE-PROCESSING PATHS ---------- //\n\n // Standardize pathConfig paths\n pathConfig = Object.entries(pathConfig).reduce<PathConfig>(\n (acc, [sharedPath, localizedPath]) => {\n if (typeof localizedPath === 'string') {\n acc[sharedPath] = localizedPath;\n } else {\n acc[sharedPath] = Object.entries(localizedPath).reduce<{\n [key: string]: string;\n }>((acc, [locale, localizedPath]) => {\n acc[gtServicesEnabled ? standardizeLocale(locale) : locale] =\n localizedPath;\n return acc;\n }, {});\n }\n return acc;\n },\n {}\n );\n\n // Standardize routeOverrides locales\n routeOverrides = Object.entries(routeOverrides).reduce<RouteOverrides>(\n (acc, [locale, paths]) => {\n acc[gtServicesEnabled ? standardizeLocale(locale) : locale] = paths;\n return acc;\n },\n {}\n );\n\n // Create the route override path mapping\n const routeOverridePathMaps = Object.entries(routeOverrides).reduce<\n Record<string, PathMatcher>\n >((acc, [locale, paths]) => {\n const overridePathConfig = Object.fromEntries(\n paths.map((path) => [path, path])\n );\n acc[locale] = createPathToSharedPathMap(\n overridePathConfig,\n true,\n defaultLocale\n ).pathToSharedPath;\n return acc;\n }, {});\n\n // Create the path mapping\n const { pathToSharedPath, defaultLocalePaths } = createPathToSharedPathMap(\n pathConfig,\n prefixDefaultLocale,\n defaultLocale\n );\n\n /**\n * Processes the incoming request to determine the user's locale and sets a locale cookie.\n * Optionally redirects the user based on the locale if locale-based routing is enabled.\n *\n * - Checks if the request URL contains a locale.\n * - Falls back to the referer URL for locale if needed.\n * - If no locale is found in the URL or referer, it checks the 'Accept-Language' header.\n * - Sets a cookie with the detected or default locale.\n * - Redirects to the correct locale route if locale routing is enabled.\n *\n * @param {NextRequest} req - The incoming request object, containing URL and headers.\n * @returns {NextResponse} - The Next.js response, either continuing the request or redirecting to the localized URL.\n */\n function middleware(req: NextRequest) {\n if (!pathnameMatchesRegex(req.nextUrl.pathname, pathRegex)) {\n return NextResponse.next();\n }\n\n // Ignore source maps\n if (\n ignoreSourceMaps &&\n req.nextUrl.pathname.startsWith(NEXT_JS_SOURCE_MAP_PATH)\n ) {\n return NextResponse.next();\n }\n\n // ---------- LOCALE DETECTION ---------- //\n\n const {\n userLocale,\n pathnameLocale,\n unstandardizedPathnameLocale,\n clearResetCookie,\n } = getLocaleFromRequest(\n req,\n defaultLocale,\n locales,\n localeRouting,\n gtServicesEnabled,\n prefixDefaultLocale,\n defaultLocalePaths,\n referrerLocaleCookieName,\n localeCookieName,\n resetLocaleCookieName,\n gt\n );\n\n const headerList = new Headers(req.headers);\n\n const responseConfig: Omit<ResponseConfig, 'type'> = {\n originalUrl: req.nextUrl,\n headerList,\n userLocale,\n clearResetCookie,\n localeRouting,\n localeRoutingEnabledCookieName,\n resetLocaleCookieName,\n localeHeaderName,\n };\n\n const getRewriteResponse = (responsePath: string) =>\n getResponse({ responsePath, type: 'rewrite', ...responseConfig });\n\n const getRedirectResponse = (responsePath: string) =>\n getResponse({ responsePath, type: 'redirect', ...responseConfig });\n\n const getNextResponse = () =>\n getResponse({ type: 'next', ...responseConfig });\n\n if (localeRouting) {\n // ---------- GET PATHS ---------- //\n\n // get pathname\n const { pathname } = req.nextUrl;\n\n // standardize pathname (ie, /tg/welcome -> /fil/welcome), (/blog -> /blog)\n const standardizedPathname =\n pathnameLocale && pathnameLocale !== unstandardizedPathnameLocale\n ? pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n `/${pathnameLocale}`\n )\n : pathname;\n\n // Get the shared path for the unprefixed pathname\n const sharedPathMatch = getSharedPath(\n standardizedPathname,\n pathToSharedPath,\n pathnameLocale\n );\n const sharedPath = sharedPathMatch?.sharedPath;\n\n // Get shared path with parameters (/en/dashboard/1/custom), for rewriting localized paths\n const sharedPathWithParameters =\n sharedPathMatch !== undefined\n ? applyTrailingSlash(\n standardizedPathname,\n replaceDynamicSegments(\n sharedPathMatch.matchedPathname,\n `/${userLocale}${sharedPath}`,\n sharedPathMatch.pathTemplate\n )\n )\n : undefined;\n\n // Localized path (/en/blog, /fr/fr-about, /fr/dashboard/[id]/custom)\n const localizedPath =\n sharedPath !== undefined\n ? getLocalizedPath(sharedPath, userLocale, pathConfig)\n : undefined;\n\n // Combine localized path with dynamic parameters (/en/blog, /fr/fr-about, /fr/dashboard/1/fr-custom)\n const localizedPathWithParameters =\n localizedPath !== undefined && sharedPathMatch !== undefined\n ? applyTrailingSlash(\n standardizedPathname,\n replaceDynamicSegments(\n sharedPathMatch.matchedPathname,\n localizedPath,\n sharedPathMatch.pathTemplate\n )\n )\n : undefined;\n\n const pagePath =\n (sharedPathWithParameters?.replace(new RegExp(`^/${userLocale}`), '') ??\n (pathnameLocale\n ? standardizedPathname.replace(new RegExp(`^/${userLocale}`), '')\n : standardizedPathname)) ||\n '/';\n const routeOverridePathMap = routeOverridePathMaps[userLocale];\n const routeOverrideMatch = routeOverridePathMap\n ? getSharedPath(pagePath, routeOverridePathMap, undefined)\n : undefined;\n const routeOverridePath =\n routeOverrideMatch !== undefined\n ? applyTrailingSlash(\n standardizedPathname,\n `/${userLocale}/${userLocale}${pagePath === '/' ? '' : pagePath}`\n )\n : undefined;\n\n // ---------- ROUTING LOGIC ---------- //\n\n // ----- CASE: no localized path exists ----- //\n\n if (localizedPathWithParameters === undefined) {\n // --- CASE: remove defaultLocale prefix --- //\n\n if (!prefixDefaultLocale && isSameDialect(userLocale, defaultLocale)) {\n if (pathnameLocale) {\n // REDIRECT CASE: used setLocale (/fr/customers -> /customers) (/en/customers -> /customers)\n if (clearResetCookie) {\n return getRedirectResponse(\n pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n ``\n ) || '/'\n );\n }\n } else {\n // REWRITE CASE: no pathnameLocale (/customers -> /en/customers)\n return getRewriteResponse(\n routeOverridePath || `/${userLocale}${pathname}`\n );\n }\n }\n\n // --- CASE: defaultLocale prefix --- //\n // REDIRECT CASE: no pathnameLocale (ie, /customers -> /fr/customers)\n else if (!pathnameLocale) {\n return getRedirectResponse(`/${userLocale}${pathname}`);\n }\n\n // REDIRECT CASE: wrong pathnameLocale (ie, /fr/customers -> /en/customers) (this usually happens after a locale switch)\n if (pathnameLocale && userLocale !== unstandardizedPathnameLocale) {\n return getRedirectResponse(\n pathname.replace(\n new RegExp(`^/${unstandardizedPathnameLocale}`),\n `/${userLocale}`\n )\n );\n }\n\n // BASE CASE: has pathnameLocale and it's correct\n if (routeOverridePath) {\n return getRewriteResponse(routeOverridePath);\n }\n return getNextResponse();\n }\n\n // ----- CASE: localized path exists ----- //\n\n if (!prefixDefaultLocale && isSameDialect(userLocale, defaultLocale)) {\n // --- CASE: remove defaultLocale prefix --- //\n\n if (pathnameLocale) {\n // REDIRECT CASE: remove locale prefix when setLocale is used (/en/blog -> /blog) (/fr/fr-about -> /en-about)\n if (clearResetCookie) {\n return getRedirectResponse(\n localizedPathWithParameters.replace(\n new RegExp(`^/${userLocale}`),\n ``\n ) || '/'\n );\n }\n } else {\n const localizedPublicPath =\n localizedPathWithParameters.replace(\n new RegExp(`^/${userLocale}`),\n ''\n ) || '/';\n\n // REDIRECT CASE: unprefixed pathname is wrong (/about -> /en-about)\n if (\n !pathnameLocale &&\n normalizePathname(localizedPublicPath) !==\n normalizePathname(pathname)\n ) {\n return getRedirectResponse(localizedPublicPath);\n }\n\n // REWRITE CASE: displaying correct path (/blog -> /en/blog)\n return getRewriteResponse(\n routeOverridePath || (sharedPathWithParameters as string)\n );\n }\n }\n\n // --- CASE: add defaultLocale prefix --- //\n\n // REDIRECT CASE: incorrect pathnameLocale\n if (\n normalizePathname(pathname) !==\n normalizePathname(localizedPathWithParameters)\n ) {\n return getRedirectResponse(localizedPathWithParameters);\n }\n\n if (routeOverridePath) {\n return getRewriteResponse(routeOverridePath);\n }\n\n // REWRITE CASE: displaying correct localized path, which is the same as the shared path (/fil/blog => /fil/blog) (/fr/fr-dashboard/1/fr-custom => /fr/dashboard/1/custom)\n // Next.js route identity preserves Unicode spelling, even when lookup\n // considers the public alias and shared path canonically equivalent.\n const rewriteUrl = new URL(\n sharedPathWithParameters as string,\n req.nextUrl\n );\n if (\n req.nextUrl.pathname !== rewriteUrl.pathname // no rewrite needed if it's already the shared path\n ) {\n // convert to shared path with dynamic parameters\n return getRewriteResponse(sharedPathWithParameters as string);\n }\n }\n\n // BASE CASE\n return getNextResponse();\n }\n\n return middleware;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { normalizePathname } from './pathname';
|
|
2
|
+
export { normalizePathname };
|
|
3
|
+
export type PathConfig = {
|
|
4
|
+
[key: string]: string | {
|
|
5
|
+
[key: string]: string;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export type PathMatch = {
|
|
9
|
+
pathTemplate: string;
|
|
10
|
+
sharedPath: string;
|
|
11
|
+
};
|
|
12
|
+
export type PathMatcherNode = {
|
|
13
|
+
staticSegments: Map<string, PathMatcherNode>;
|
|
14
|
+
dynamicSegment?: PathMatcherNode;
|
|
15
|
+
catchAllSegment?: PathMatcherNode;
|
|
16
|
+
optionalCatchAllSegment?: PathMatcherNode;
|
|
17
|
+
match?: PathMatch;
|
|
18
|
+
};
|
|
19
|
+
export type PathMatcher = {
|
|
20
|
+
root: PathMatcherNode;
|
|
21
|
+
localizedRoot?: PathMatcherNode;
|
|
22
|
+
defaultLocaleRoot?: PathMatcherNode;
|
|
23
|
+
};
|
|
24
|
+
export type DynamicSegmentType = 'dynamic' | 'catch-all' | 'optional-catch-all';
|
|
25
|
+
/** Identifies the supported Next.js dynamic segment syntax. */
|
|
26
|
+
export declare function getDynamicSegmentType(segment: string): DynamicSegmentType | undefined;
|
|
27
|
+
/** Splits a pathname without interpreting encoded literals as route syntax. */
|
|
28
|
+
export declare function getPathSegments(pathname: string): string[];
|
|
29
|
+
/** Builds a segment trie from pathname-to-shared-path entries. */
|
|
30
|
+
export declare function createPathMatcher(pathEntries: ReadonlyArray<readonly [string, string]>): PathMatcher;
|
|
31
|
+
/** Creates matchers for localized paths and unprefixed default-locale paths. */
|
|
32
|
+
export declare function createPathToSharedPathMap(pathConfig: PathConfig, prefixDefaultLocale: boolean, defaultLocale: string): {
|
|
33
|
+
pathToSharedPath: PathMatcher;
|
|
34
|
+
defaultLocalePaths: PathMatcher;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=createPathMatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createPathMatcher.d.ts","sourceRoot":"","sources":["../../src/middleware-dir/createPathMatcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAwB,MAAM,YAAY,CAAC;AAErE,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAE7B,MAAM,MAAM,UAAU,GAAG;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC7C,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,uBAAuB,CAAC,EAAE,eAAe,CAAC;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,eAAe,CAAC;IACtB,aAAa,CAAC,EAAE,eAAe,CAAC;IAChC,iBAAiB,CAAC,EAAE,eAAe,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,WAAW,GAAG,oBAAoB,CAAC;AAWhF,+DAA+D;AAC/D,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,GACd,kBAAkB,GAAG,SAAS,CAOhC;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAO1D;AAmCD,kEAAkE;AAClE,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACpD,WAAW,CAMb;AAED,gFAAgF;AAChF,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,UAAU,EACtB,mBAAmB,EAAE,OAAO,EAC5B,aAAa,EAAE,MAAM,GACpB;IACD,gBAAgB,EAAE,WAAW,CAAC;IAC9B,kBAAkB,EAAE,WAAW,CAAC;CACjC,CA2BA"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_middleware_dir_pathname = require("./pathname.js");
|
|
3
|
+
//#region src/middleware-dir/createPathMatcher.ts
|
|
4
|
+
const DYNAMIC_SEGMENT_PATTERN = /^\[[^.[\]/][^\]/]*\]$/;
|
|
5
|
+
const CATCH_ALL_SEGMENT_PATTERN = /^\[\.\.\.[^\]/]+\]$/;
|
|
6
|
+
const OPTIONAL_CATCH_ALL_SEGMENT_PATTERN = /^\[\[\.\.\.[^\]/]+\]\]$/;
|
|
7
|
+
/** Creates an empty node in the pathname matcher trie. */
|
|
8
|
+
function createPathMatcherNode() {
|
|
9
|
+
return { staticSegments: /* @__PURE__ */ new Map() };
|
|
10
|
+
}
|
|
11
|
+
/** Identifies the supported Next.js dynamic segment syntax. */
|
|
12
|
+
function getDynamicSegmentType(segment) {
|
|
13
|
+
if (OPTIONAL_CATCH_ALL_SEGMENT_PATTERN.test(segment)) return "optional-catch-all";
|
|
14
|
+
if (CATCH_ALL_SEGMENT_PATTERN.test(segment)) return "catch-all";
|
|
15
|
+
if (DYNAMIC_SEGMENT_PATTERN.test(segment)) return "dynamic";
|
|
16
|
+
}
|
|
17
|
+
/** Splits a pathname without interpreting encoded literals as route syntax. */
|
|
18
|
+
function getPathSegments(pathname) {
|
|
19
|
+
if (pathname === "") return [];
|
|
20
|
+
const normalizedPathname = require_middleware_dir_pathname.stripTrailingSlashes(pathname);
|
|
21
|
+
return (normalizedPathname.startsWith("/") ? normalizedPathname.slice(1) : normalizedPathname).split("/");
|
|
22
|
+
}
|
|
23
|
+
/** Inserts a pathname template and its shared path into the matcher trie. */
|
|
24
|
+
function insertPath(matcher, pathname, sharedPath) {
|
|
25
|
+
let node = matcher.root;
|
|
26
|
+
for (const segment of getPathSegments(pathname)) {
|
|
27
|
+
const segmentType = getDynamicSegmentType(segment);
|
|
28
|
+
if (segmentType === "dynamic") {
|
|
29
|
+
node.dynamicSegment ||= createPathMatcherNode();
|
|
30
|
+
node = node.dynamicSegment;
|
|
31
|
+
} else if (segmentType === "catch-all") {
|
|
32
|
+
node.catchAllSegment ||= createPathMatcherNode();
|
|
33
|
+
node = node.catchAllSegment;
|
|
34
|
+
} else if (segmentType === "optional-catch-all") {
|
|
35
|
+
node.optionalCatchAllSegment ||= createPathMatcherNode();
|
|
36
|
+
node = node.optionalCatchAllSegment;
|
|
37
|
+
} else {
|
|
38
|
+
const staticSegment = require_middleware_dir_pathname.normalizePathname(segment);
|
|
39
|
+
const staticNode = node.staticSegments.get(staticSegment) || createPathMatcherNode();
|
|
40
|
+
node.staticSegments.set(staticSegment, staticNode);
|
|
41
|
+
node = staticNode;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
node.match = {
|
|
45
|
+
pathTemplate: pathname,
|
|
46
|
+
sharedPath
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Builds a segment trie from pathname-to-shared-path entries. */
|
|
50
|
+
function createPathMatcher(pathEntries) {
|
|
51
|
+
const matcher = { root: createPathMatcherNode() };
|
|
52
|
+
for (const [pathname, sharedPath] of pathEntries) insertPath(matcher, pathname, sharedPath);
|
|
53
|
+
return matcher;
|
|
54
|
+
}
|
|
55
|
+
/** Creates matchers for localized paths and unprefixed default-locale paths. */
|
|
56
|
+
function createPathToSharedPathMap(pathConfig, prefixDefaultLocale, defaultLocale) {
|
|
57
|
+
const pathEntries = [];
|
|
58
|
+
const localizedEntries = [];
|
|
59
|
+
const defaultLocaleEntries = [];
|
|
60
|
+
for (const [sharedPath, localizedPaths] of Object.entries(pathConfig)) {
|
|
61
|
+
pathEntries.push([sharedPath, sharedPath]);
|
|
62
|
+
if (typeof localizedPaths === "object") for (const [locale, localizedPath] of Object.entries(localizedPaths)) {
|
|
63
|
+
localizedEntries.push([`/${locale}${localizedPath}`, sharedPath]);
|
|
64
|
+
if (!prefixDefaultLocale && locale === defaultLocale) defaultLocaleEntries.push([localizedPath, sharedPath]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const defaultLocalePaths = createPathMatcher(defaultLocaleEntries);
|
|
68
|
+
return {
|
|
69
|
+
pathToSharedPath: {
|
|
70
|
+
...createPathMatcher(pathEntries),
|
|
71
|
+
localizedRoot: createPathMatcher(localizedEntries).root,
|
|
72
|
+
defaultLocaleRoot: defaultLocalePaths.root
|
|
73
|
+
},
|
|
74
|
+
defaultLocalePaths
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
exports.createPathMatcher = createPathMatcher;
|
|
79
|
+
exports.createPathToSharedPathMap = createPathToSharedPathMap;
|
|
80
|
+
exports.getDynamicSegmentType = getDynamicSegmentType;
|
|
81
|
+
exports.getPathSegments = getPathSegments;
|
|
82
|
+
exports.normalizePathname = require_middleware_dir_pathname.normalizePathname;
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=createPathMatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createPathMatcher.js","names":["stripTrailingSlashes","normalizePathname"],"sources":["../../src/middleware-dir/createPathMatcher.ts"],"sourcesContent":["import { normalizePathname, stripTrailingSlashes } from './pathname';\n\nexport { normalizePathname };\n\nexport type PathConfig = {\n [key: string]: string | { [key: string]: string };\n};\n\nexport type PathMatch = {\n pathTemplate: string;\n sharedPath: string;\n};\n\nexport type PathMatcherNode = {\n staticSegments: Map<string, PathMatcherNode>;\n dynamicSegment?: PathMatcherNode;\n catchAllSegment?: PathMatcherNode;\n optionalCatchAllSegment?: PathMatcherNode;\n match?: PathMatch;\n};\n\nexport type PathMatcher = {\n root: PathMatcherNode;\n localizedRoot?: PathMatcherNode;\n defaultLocaleRoot?: PathMatcherNode;\n};\n\nexport type DynamicSegmentType = 'dynamic' | 'catch-all' | 'optional-catch-all';\n\nconst DYNAMIC_SEGMENT_PATTERN = /^\\[[^.[\\]/][^\\]/]*\\]$/;\nconst CATCH_ALL_SEGMENT_PATTERN = /^\\[\\.\\.\\.[^\\]/]+\\]$/;\nconst OPTIONAL_CATCH_ALL_SEGMENT_PATTERN = /^\\[\\[\\.\\.\\.[^\\]/]+\\]\\]$/;\n\n/** Creates an empty node in the pathname matcher trie. */\nfunction createPathMatcherNode(): PathMatcherNode {\n return { staticSegments: new Map() };\n}\n\n/** Identifies the supported Next.js dynamic segment syntax. */\nexport function getDynamicSegmentType(\n segment: string\n): DynamicSegmentType | undefined {\n if (OPTIONAL_CATCH_ALL_SEGMENT_PATTERN.test(segment)) {\n return 'optional-catch-all';\n }\n if (CATCH_ALL_SEGMENT_PATTERN.test(segment)) return 'catch-all';\n if (DYNAMIC_SEGMENT_PATTERN.test(segment)) return 'dynamic';\n return undefined;\n}\n\n/** Splits a pathname without interpreting encoded literals as route syntax. */\nexport function getPathSegments(pathname: string): string[] {\n if (pathname === '') return [];\n const normalizedPathname = stripTrailingSlashes(pathname);\n const pathnameWithoutLeadingSlash = normalizedPathname.startsWith('/')\n ? normalizedPathname.slice(1)\n : normalizedPathname;\n return pathnameWithoutLeadingSlash.split('/');\n}\n\n/** Inserts a pathname template and its shared path into the matcher trie. */\nfunction insertPath(\n matcher: PathMatcher,\n pathname: string,\n sharedPath: string\n) {\n let node = matcher.root;\n\n for (const segment of getPathSegments(pathname)) {\n const segmentType = getDynamicSegmentType(segment);\n if (segmentType === 'dynamic') {\n node.dynamicSegment ||= createPathMatcherNode();\n node = node.dynamicSegment;\n } else if (segmentType === 'catch-all') {\n node.catchAllSegment ||= createPathMatcherNode();\n node = node.catchAllSegment;\n } else if (segmentType === 'optional-catch-all') {\n node.optionalCatchAllSegment ||= createPathMatcherNode();\n node = node.optionalCatchAllSegment;\n } else {\n const staticSegment = normalizePathname(segment);\n const staticNode =\n node.staticSegments.get(staticSegment) || createPathMatcherNode();\n node.staticSegments.set(staticSegment, staticNode);\n node = staticNode;\n }\n }\n\n // Match the previous map behavior when two parameter names describe the\n // same path shape: the later entry wins.\n node.match = { pathTemplate: pathname, sharedPath };\n}\n\n/** Builds a segment trie from pathname-to-shared-path entries. */\nexport function createPathMatcher(\n pathEntries: ReadonlyArray<readonly [string, string]>\n): PathMatcher {\n const matcher: PathMatcher = { root: createPathMatcherNode() };\n for (const [pathname, sharedPath] of pathEntries) {\n insertPath(matcher, pathname, sharedPath);\n }\n return matcher;\n}\n\n/** Creates matchers for localized paths and unprefixed default-locale paths. */\nexport function createPathToSharedPathMap(\n pathConfig: PathConfig,\n prefixDefaultLocale: boolean,\n defaultLocale: string\n): {\n pathToSharedPath: PathMatcher;\n defaultLocalePaths: PathMatcher;\n} {\n const pathEntries: Array<readonly [string, string]> = [];\n const localizedEntries: Array<readonly [string, string]> = [];\n const defaultLocaleEntries: Array<readonly [string, string]> = [];\n\n for (const [sharedPath, localizedPaths] of Object.entries(pathConfig)) {\n pathEntries.push([sharedPath, sharedPath]);\n\n if (typeof localizedPaths === 'object') {\n for (const [locale, localizedPath] of Object.entries(localizedPaths)) {\n localizedEntries.push([`/${locale}${localizedPath}`, sharedPath]);\n if (!prefixDefaultLocale && locale === defaultLocale) {\n defaultLocaleEntries.push([localizedPath, sharedPath]);\n }\n }\n }\n }\n\n const defaultLocalePaths = createPathMatcher(defaultLocaleEntries);\n return {\n pathToSharedPath: {\n ...createPathMatcher(pathEntries),\n localizedRoot: createPathMatcher(localizedEntries).root,\n defaultLocaleRoot: defaultLocalePaths.root,\n },\n defaultLocalePaths,\n };\n}\n"],"mappings":";;;AA6BA,MAAM,0BAA0B;AAChC,MAAM,4BAA4B;AAClC,MAAM,qCAAqC;;AAG3C,SAAS,wBAAyC;AAChD,QAAO,EAAE,gCAAgB,IAAI,KAAK,EAAE;;;AAItC,SAAgB,sBACd,SACgC;AAChC,KAAI,mCAAmC,KAAK,QAAQ,CAClD,QAAO;AAET,KAAI,0BAA0B,KAAK,QAAQ,CAAE,QAAO;AACpD,KAAI,wBAAwB,KAAK,QAAQ,CAAE,QAAO;;;AAKpD,SAAgB,gBAAgB,UAA4B;AAC1D,KAAI,aAAa,GAAI,QAAO,EAAE;CAC9B,MAAM,qBAAqBA,gCAAAA,qBAAqB,SAAS;AAIzD,SAHoC,mBAAmB,WAAW,IAAI,GAClE,mBAAmB,MAAM,EAAE,GAC3B,oBAC+B,MAAM,IAAI;;;AAI/C,SAAS,WACP,SACA,UACA,YACA;CACA,IAAI,OAAO,QAAQ;AAEnB,MAAK,MAAM,WAAW,gBAAgB,SAAS,EAAE;EAC/C,MAAM,cAAc,sBAAsB,QAAQ;AAClD,MAAI,gBAAgB,WAAW;AAC7B,QAAK,mBAAmB,uBAAuB;AAC/C,UAAO,KAAK;aACH,gBAAgB,aAAa;AACtC,QAAK,oBAAoB,uBAAuB;AAChD,UAAO,KAAK;aACH,gBAAgB,sBAAsB;AAC/C,QAAK,4BAA4B,uBAAuB;AACxD,UAAO,KAAK;SACP;GACL,MAAM,gBAAgBC,gCAAAA,kBAAkB,QAAQ;GAChD,MAAM,aACJ,KAAK,eAAe,IAAI,cAAc,IAAI,uBAAuB;AACnE,QAAK,eAAe,IAAI,eAAe,WAAW;AAClD,UAAO;;;AAMX,MAAK,QAAQ;EAAE,cAAc;EAAU;EAAY;;;AAIrD,SAAgB,kBACd,aACa;CACb,MAAM,UAAuB,EAAE,MAAM,uBAAuB,EAAE;AAC9D,MAAK,MAAM,CAAC,UAAU,eAAe,YACnC,YAAW,SAAS,UAAU,WAAW;AAE3C,QAAO;;;AAIT,SAAgB,0BACd,YACA,qBACA,eAIA;CACA,MAAM,cAAgD,EAAE;CACxD,MAAM,mBAAqD,EAAE;CAC7D,MAAM,uBAAyD,EAAE;AAEjE,MAAK,MAAM,CAAC,YAAY,mBAAmB,OAAO,QAAQ,WAAW,EAAE;AACrE,cAAY,KAAK,CAAC,YAAY,WAAW,CAAC;AAE1C,MAAI,OAAO,mBAAmB,SAC5B,MAAK,MAAM,CAAC,QAAQ,kBAAkB,OAAO,QAAQ,eAAe,EAAE;AACpE,oBAAiB,KAAK,CAAC,IAAI,SAAS,iBAAiB,WAAW,CAAC;AACjE,OAAI,CAAC,uBAAuB,WAAW,cACrC,sBAAqB,KAAK,CAAC,eAAe,WAAW,CAAC;;;CAM9D,MAAM,qBAAqB,kBAAkB,qBAAqB;AAClE,QAAO;EACL,kBAAkB;GAChB,GAAG,kBAAkB,YAAY;GACjC,eAAe,kBAAkB,iBAAiB,CAAC;GACnD,mBAAmB,mBAAmB;GACvC;EACD;EACD"}
|