@depup/next-intl 4.8.3-depup.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/LICENSE +21 -0
- package/README.md +33 -0
- package/changes.json +18 -0
- package/config.d.ts +4 -0
- package/dist/cjs/development/ExtractorCodec-D9Tw618d.cjs +7 -0
- package/dist/cjs/development/JSONCodec-rhejs716.cjs +50 -0
- package/dist/cjs/development/POCodec-Cv3VdXQG.cjs +105 -0
- package/dist/cjs/development/plugin-YzBhQjAo.cjs +1457 -0
- package/dist/cjs/development/plugin.cjs +13 -0
- package/dist/esm/development/config.js +5 -0
- package/dist/esm/development/extractor/ExtractionCompiler.js +38 -0
- package/dist/esm/development/extractor/catalog/CatalogLocales.js +84 -0
- package/dist/esm/development/extractor/catalog/CatalogManager.js +370 -0
- package/dist/esm/development/extractor/catalog/CatalogPersister.js +63 -0
- package/dist/esm/development/extractor/catalog/SaveScheduler.js +85 -0
- package/dist/esm/development/extractor/catalogLoader.js +96 -0
- package/dist/esm/development/extractor/extractMessages.js +15 -0
- package/dist/esm/development/extractor/extractionLoader.js +26 -0
- package/dist/esm/development/extractor/extractor/LRUCache.js +30 -0
- package/dist/esm/development/extractor/extractor/MessageExtractor.js +66 -0
- package/dist/esm/development/extractor/format/ExtractorCodec.js +5 -0
- package/dist/esm/development/extractor/format/codecs/JSONCodec.js +42 -0
- package/dist/esm/development/extractor/format/codecs/POCodec.js +93 -0
- package/dist/esm/development/extractor/format/index.js +44 -0
- package/dist/esm/development/extractor/source/SourceFileFilter.js +29 -0
- package/dist/esm/development/extractor/source/SourceFileScanner.js +31 -0
- package/dist/esm/development/extractor/source/SourceFileWatcher.js +132 -0
- package/dist/esm/development/extractor/utils.js +46 -0
- package/dist/esm/development/extractor.js +2 -0
- package/dist/esm/development/index.react-client.js +4 -0
- package/dist/esm/development/index.react-server.js +9 -0
- package/dist/esm/development/middleware/getAlternateLinksHeaderValue.js +91 -0
- package/dist/esm/development/middleware/middleware.js +168 -0
- package/dist/esm/development/middleware/resolveLocale.js +129 -0
- package/dist/esm/development/middleware/syncCookie.js +27 -0
- package/dist/esm/development/middleware/utils.js +194 -0
- package/dist/esm/development/middleware.js +1 -0
- package/dist/esm/development/navigation/react-client/createNavigation.js +80 -0
- package/dist/esm/development/navigation/react-client/useBasePathname.js +36 -0
- package/dist/esm/development/navigation/react-server/createNavigation.js +22 -0
- package/dist/esm/development/navigation/react-server/getServerLocale.js +12 -0
- package/dist/esm/development/navigation/shared/BaseLink.js +51 -0
- package/dist/esm/development/navigation/shared/createSharedNavigationFns.js +105 -0
- package/dist/esm/development/navigation/shared/syncLocaleCookie.js +47 -0
- package/dist/esm/development/navigation/shared/utils.js +165 -0
- package/dist/esm/development/navigation.react-client.js +1 -0
- package/dist/esm/development/navigation.react-server.js +1 -0
- package/dist/esm/development/plugin/config.js +10 -0
- package/dist/esm/development/plugin/createNextIntlPlugin.js +26 -0
- package/dist/esm/development/plugin/declaration/createMessagesDeclaration.js +70 -0
- package/dist/esm/development/plugin/extractor/initExtractionCompiler.js +59 -0
- package/dist/esm/development/plugin/getNextConfig.js +267 -0
- package/dist/esm/development/plugin/nextFlags.js +32 -0
- package/dist/esm/development/plugin/utils.js +26 -0
- package/dist/esm/development/plugin/watchFile.js +21 -0
- package/dist/esm/development/plugin.js +1 -0
- package/dist/esm/development/react-client/index.js +34 -0
- package/dist/esm/development/react-server/NextIntlClientProviderServer.js +34 -0
- package/dist/esm/development/react-server/useConfig.js +21 -0
- package/dist/esm/development/react-server/useExtracted.js +9 -0
- package/dist/esm/development/react-server/useFormatter.js +9 -0
- package/dist/esm/development/react-server/useLocale.js +8 -0
- package/dist/esm/development/react-server/useMessages.js +9 -0
- package/dist/esm/development/react-server/useNow.js +12 -0
- package/dist/esm/development/react-server/useTimeZone.js +8 -0
- package/dist/esm/development/react-server/useTranslations.js +9 -0
- package/dist/esm/development/routing/config.js +26 -0
- package/dist/esm/development/routing/defineRouting.js +25 -0
- package/dist/esm/development/routing.js +1 -0
- package/dist/esm/development/server/react-client/index.js +32 -0
- package/dist/esm/development/server/react-server/RequestLocale.js +36 -0
- package/dist/esm/development/server/react-server/RequestLocaleCache.js +18 -0
- package/dist/esm/development/server/react-server/createRequestConfig.js +2 -0
- package/dist/esm/development/server/react-server/getConfig.js +59 -0
- package/dist/esm/development/server/react-server/getConfigNow.js +10 -0
- package/dist/esm/development/server/react-server/getDefaultNow.js +9 -0
- package/dist/esm/development/server/react-server/getExtracted.js +24 -0
- package/dist/esm/development/server/react-server/getFormats.js +10 -0
- package/dist/esm/development/server/react-server/getFormatter.js +21 -0
- package/dist/esm/development/server/react-server/getLocale.js +10 -0
- package/dist/esm/development/server/react-server/getMessages.js +19 -0
- package/dist/esm/development/server/react-server/getNow.js +8 -0
- package/dist/esm/development/server/react-server/getRequestConfig.js +8 -0
- package/dist/esm/development/server/react-server/getServerExtractor.js +38 -0
- package/dist/esm/development/server/react-server/getServerFormatter.js +17 -0
- package/dist/esm/development/server/react-server/getServerTranslator.js +12 -0
- package/dist/esm/development/server/react-server/getTimeZone.js +13 -0
- package/dist/esm/development/server/react-server/getTranslations.js +28 -0
- package/dist/esm/development/server/react-server/validateLocale.js +12 -0
- package/dist/esm/development/server.react-client.js +1 -0
- package/dist/esm/development/server.react-server.js +9 -0
- package/dist/esm/development/shared/NextIntlClientProvider.js +18 -0
- package/dist/esm/development/shared/constants.js +4 -0
- package/dist/esm/development/shared/use.js +13 -0
- package/dist/esm/development/shared/utils.js +140 -0
- package/dist/esm/production/config.js +1 -0
- package/dist/esm/production/extractor/ExtractionCompiler.js +1 -0
- package/dist/esm/production/extractor/catalog/CatalogLocales.js +1 -0
- package/dist/esm/production/extractor/catalog/CatalogManager.js +1 -0
- package/dist/esm/production/extractor/catalog/CatalogPersister.js +1 -0
- package/dist/esm/production/extractor/catalog/SaveScheduler.js +1 -0
- package/dist/esm/production/extractor/catalogLoader.js +1 -0
- package/dist/esm/production/extractor/extractMessages.js +1 -0
- package/dist/esm/production/extractor/extractionLoader.js +1 -0
- package/dist/esm/production/extractor/extractor/LRUCache.js +1 -0
- package/dist/esm/production/extractor/extractor/MessageExtractor.js +1 -0
- package/dist/esm/production/extractor/format/ExtractorCodec.js +1 -0
- package/dist/esm/production/extractor/format/codecs/JSONCodec.js +1 -0
- package/dist/esm/production/extractor/format/codecs/POCodec.js +1 -0
- package/dist/esm/production/extractor/format/index.js +1 -0
- package/dist/esm/production/extractor/source/SourceFileFilter.js +1 -0
- package/dist/esm/production/extractor/source/SourceFileScanner.js +1 -0
- package/dist/esm/production/extractor/source/SourceFileWatcher.js +1 -0
- package/dist/esm/production/extractor/utils.js +1 -0
- package/dist/esm/production/extractor.js +1 -0
- package/dist/esm/production/index.react-client.js +1 -0
- package/dist/esm/production/index.react-server.js +1 -0
- package/dist/esm/production/middleware/getAlternateLinksHeaderValue.js +1 -0
- package/dist/esm/production/middleware/middleware.js +1 -0
- package/dist/esm/production/middleware/resolveLocale.js +1 -0
- package/dist/esm/production/middleware/syncCookie.js +1 -0
- package/dist/esm/production/middleware/utils.js +1 -0
- package/dist/esm/production/middleware.js +1 -0
- package/dist/esm/production/navigation/react-client/createNavigation.js +1 -0
- package/dist/esm/production/navigation/react-client/useBasePathname.js +1 -0
- package/dist/esm/production/navigation/react-server/createNavigation.js +1 -0
- package/dist/esm/production/navigation/react-server/getServerLocale.js +1 -0
- package/dist/esm/production/navigation/shared/BaseLink.js +2 -0
- package/dist/esm/production/navigation/shared/createSharedNavigationFns.js +1 -0
- package/dist/esm/production/navigation/shared/syncLocaleCookie.js +1 -0
- package/dist/esm/production/navigation/shared/utils.js +1 -0
- package/dist/esm/production/navigation.react-client.js +1 -0
- package/dist/esm/production/navigation.react-server.js +1 -0
- package/dist/esm/production/plugin/config.js +1 -0
- package/dist/esm/production/plugin/createNextIntlPlugin.js +1 -0
- package/dist/esm/production/plugin/declaration/createMessagesDeclaration.js +1 -0
- package/dist/esm/production/plugin/extractor/initExtractionCompiler.js +1 -0
- package/dist/esm/production/plugin/getNextConfig.js +1 -0
- package/dist/esm/production/plugin/nextFlags.js +1 -0
- package/dist/esm/production/plugin/utils.js +1 -0
- package/dist/esm/production/plugin/watchFile.js +1 -0
- package/dist/esm/production/plugin.js +1 -0
- package/dist/esm/production/react-client/index.js +1 -0
- package/dist/esm/production/react-server/NextIntlClientProviderServer.js +1 -0
- package/dist/esm/production/react-server/useConfig.js +1 -0
- package/dist/esm/production/react-server/useExtracted.js +1 -0
- package/dist/esm/production/react-server/useFormatter.js +1 -0
- package/dist/esm/production/react-server/useLocale.js +1 -0
- package/dist/esm/production/react-server/useMessages.js +1 -0
- package/dist/esm/production/react-server/useNow.js +1 -0
- package/dist/esm/production/react-server/useTimeZone.js +1 -0
- package/dist/esm/production/react-server/useTranslations.js +1 -0
- package/dist/esm/production/routing/config.js +1 -0
- package/dist/esm/production/routing/defineRouting.js +1 -0
- package/dist/esm/production/routing.js +1 -0
- package/dist/esm/production/server/react-client/index.js +1 -0
- package/dist/esm/production/server/react-server/RequestLocale.js +1 -0
- package/dist/esm/production/server/react-server/RequestLocaleCache.js +1 -0
- package/dist/esm/production/server/react-server/createRequestConfig.js +1 -0
- package/dist/esm/production/server/react-server/getConfig.js +1 -0
- package/dist/esm/production/server/react-server/getConfigNow.js +1 -0
- package/dist/esm/production/server/react-server/getDefaultNow.js +1 -0
- package/dist/esm/production/server/react-server/getExtracted.js +1 -0
- package/dist/esm/production/server/react-server/getFormats.js +1 -0
- package/dist/esm/production/server/react-server/getFormatter.js +1 -0
- package/dist/esm/production/server/react-server/getLocale.js +1 -0
- package/dist/esm/production/server/react-server/getMessages.js +1 -0
- package/dist/esm/production/server/react-server/getNow.js +1 -0
- package/dist/esm/production/server/react-server/getRequestConfig.js +1 -0
- package/dist/esm/production/server/react-server/getServerExtractor.js +1 -0
- package/dist/esm/production/server/react-server/getServerFormatter.js +1 -0
- package/dist/esm/production/server/react-server/getServerTranslator.js +1 -0
- package/dist/esm/production/server/react-server/getTimeZone.js +1 -0
- package/dist/esm/production/server/react-server/getTranslations.js +1 -0
- package/dist/esm/production/server.react-client.js +1 -0
- package/dist/esm/production/server.react-server.js +1 -0
- package/dist/esm/production/shared/NextIntlClientProvider.js +2 -0
- package/dist/esm/production/shared/constants.js +1 -0
- package/dist/esm/production/shared/use.js +1 -0
- package/dist/esm/production/shared/utils.js +1 -0
- package/dist/types/config.d.ts +1 -0
- package/dist/types/extractor/ExtractionCompiler.d.ts +15 -0
- package/dist/types/extractor/catalog/CatalogLocales.d.ts +29 -0
- package/dist/types/extractor/catalog/CatalogManager.d.ts +59 -0
- package/dist/types/extractor/catalog/CatalogPersister.d.ts +20 -0
- package/dist/types/extractor/catalog/SaveScheduler.d.ts +18 -0
- package/dist/types/extractor/extractMessages.d.ts +2 -0
- package/dist/types/extractor/extractor/KeyGenerator.d.ts +3 -0
- package/dist/types/extractor/extractor/LRUCache.d.ts +7 -0
- package/dist/types/extractor/extractor/MessageExtractor.bench.d.ts +1 -0
- package/dist/types/extractor/extractor/MessageExtractor.d.ts +21 -0
- package/dist/types/extractor/format/ExtractorCodec.d.ts +33 -0
- package/dist/types/extractor/format/codecs/JSONCodec.d.ts +2 -0
- package/dist/types/extractor/format/codecs/POCodec.d.ts +2 -0
- package/dist/types/extractor/format/codecs/fixtures/JSONCodecStructured.d.ts +2 -0
- package/dist/types/extractor/format/codecs/fixtures/POCodecSourceMessageKey.d.ts +2 -0
- package/dist/types/extractor/format/index.d.ts +15 -0
- package/dist/types/extractor/format/types.d.ts +8 -0
- package/dist/types/extractor/index.d.ts +2 -0
- package/dist/types/extractor/source/SourceFileFilter.d.ts +8 -0
- package/dist/types/extractor/source/SourceFileScanner.d.ts +4 -0
- package/dist/types/extractor/source/SourceFileWatcher.d.ts +15 -0
- package/dist/types/extractor/types.d.ts +28 -0
- package/dist/types/extractor/utils.d.ts +7 -0
- package/dist/types/extractor.d.ts +1 -0
- package/dist/types/index.react-client.d.ts +8 -0
- package/dist/types/index.react-server.d.ts +1 -0
- package/dist/types/middleware/getAlternateLinksHeaderValue.d.ts +13 -0
- package/dist/types/middleware/index.d.ts +4 -0
- package/dist/types/middleware/middleware.d.ts +4 -0
- package/dist/types/middleware/resolveLocale.d.ts +9 -0
- package/dist/types/middleware/syncCookie.d.ts +7 -0
- package/dist/types/middleware/utils.d.ts +26 -0
- package/dist/types/middleware.d.ts +1 -0
- package/dist/types/navigation/react-client/createNavigation.d.ts +464 -0
- package/dist/types/navigation/react-client/index.d.ts +2 -0
- package/dist/types/navigation/react-client/useBasePathname.d.ts +5 -0
- package/dist/types/navigation/react-server/createNavigation.d.ts +384 -0
- package/dist/types/navigation/react-server/getServerLocale.d.ts +5 -0
- package/dist/types/navigation/react-server/index.d.ts +2 -0
- package/dist/types/navigation/shared/BaseLink.d.ts +11 -0
- package/dist/types/navigation/shared/StrictParams.d.ts +8 -0
- package/dist/types/navigation/shared/createSharedNavigationFns.d.ts +370 -0
- package/dist/types/navigation/shared/syncLocaleCookie.d.ts +8 -0
- package/dist/types/navigation/shared/utils.d.ts +53 -0
- package/dist/types/navigation.react-client.d.ts +1 -0
- package/dist/types/navigation.react-server.d.ts +1 -0
- package/dist/types/plugin/catalog/catalogLoader.d.ts +10 -0
- package/dist/types/plugin/config.d.ts +3 -0
- package/dist/types/plugin/createNextIntlPlugin.d.ts +3 -0
- package/dist/types/plugin/declaration/createMessagesDeclaration.d.ts +1 -0
- package/dist/types/plugin/declaration/index.d.ts +1 -0
- package/dist/types/plugin/extractor/extractionLoader.d.ts +3 -0
- package/dist/types/plugin/extractor/initExtractionCompiler.d.ts +2 -0
- package/dist/types/plugin/getNextConfig.d.ts +3 -0
- package/dist/types/plugin/index.d.ts +1 -0
- package/dist/types/plugin/nextFlags.d.ts +2 -0
- package/dist/types/plugin/types.d.ts +29 -0
- package/dist/types/plugin/utils.d.ts +8 -0
- package/dist/types/plugin/watchFile.d.ts +6 -0
- package/dist/types/plugin.d.ts +1 -0
- package/dist/types/react-client/index.d.ts +16 -0
- package/dist/types/react-server/NextIntlClientProviderServer.d.ts +5 -0
- package/dist/types/react-server/index.d.ts +16 -0
- package/dist/types/react-server/testUtils.d.ts +2 -0
- package/dist/types/react-server/useConfig.d.ts +2 -0
- package/dist/types/react-server/useExtracted.d.ts +2 -0
- package/dist/types/react-server/useFormatter.d.ts +2 -0
- package/dist/types/react-server/useLocale.d.ts +2 -0
- package/dist/types/react-server/useMessages.d.ts +2 -0
- package/dist/types/react-server/useNow.d.ts +2 -0
- package/dist/types/react-server/useTimeZone.d.ts +2 -0
- package/dist/types/react-server/useTranslations.d.ts +2 -0
- package/dist/types/routing/config.d.ts +73 -0
- package/dist/types/routing/defineRouting.d.ts +3 -0
- package/dist/types/routing/index.d.ts +3 -0
- package/dist/types/routing/types.d.ts +24 -0
- package/dist/types/routing.d.ts +1 -0
- package/dist/types/server/react-client/index.d.ts +10 -0
- package/dist/types/server/react-server/RequestLocale.d.ts +1 -0
- package/dist/types/server/react-server/RequestLocaleCache.d.ts +3 -0
- package/dist/types/server/react-server/createRequestConfig.d.ts +3 -0
- package/dist/types/server/react-server/getConfig.d.ts +13 -0
- package/dist/types/server/react-server/getConfigNow.d.ts +4 -0
- package/dist/types/server/react-server/getDefaultNow.d.ts +3 -0
- package/dist/types/server/react-server/getExtracted.d.ts +10 -0
- package/dist/types/server/react-server/getFormats.d.ts +3 -0
- package/dist/types/server/react-server/getFormatter.d.ts +10 -0
- package/dist/types/server/react-server/getLocale.d.ts +4 -0
- package/dist/types/server/react-server/getMessages.d.ts +6 -0
- package/dist/types/server/react-server/getNow.d.ts +4 -0
- package/dist/types/server/react-server/getRequestConfig.d.ts +35 -0
- package/dist/types/server/react-server/getServerExtractor.d.ts +5 -0
- package/dist/types/server/react-server/getServerFormatter.d.ts +25 -0
- package/dist/types/server/react-server/getServerTranslator.d.ts +4 -0
- package/dist/types/server/react-server/getTimeZone.d.ts +4 -0
- package/dist/types/server/react-server/getTranslations.d.ts +8 -0
- package/dist/types/server/react-server/index.d.ts +12 -0
- package/dist/types/server/react-server/validateLocale.d.ts +1 -0
- package/dist/types/server.react-client.d.ts +1 -0
- package/dist/types/server.react-server.d.ts +1 -0
- package/dist/types/shared/NextIntlClientProvider.d.ts +9 -0
- package/dist/types/shared/constants.d.ts +1 -0
- package/dist/types/shared/types.d.ts +5 -0
- package/dist/types/shared/use.d.ts +3 -0
- package/dist/types/shared/utils.d.ts +20 -0
- package/extractor/catalogLoader.d.ts +4 -0
- package/extractor/extractionLoader.d.ts +4 -0
- package/extractor.d.ts +2 -0
- package/middleware.d.ts +4 -0
- package/navigation.d.ts +2 -0
- package/package.json +175 -0
- package/plugin.d.cts +3 -0
- package/plugin.d.ts +4 -0
- package/routing.d.ts +2 -0
- package/server.d.ts +2 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { normalizeTrailingSlash } from '../shared/utils.js';
|
|
2
|
+
import { getHost, getNormalizedPathname, getLocalePrefixes, isLocaleSupportedOnDomain, applyBasePath, formatTemplatePathname } from './utils.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* See https://developers.google.com/search/docs/specialty/international/localized-versions
|
|
6
|
+
*/
|
|
7
|
+
function getAlternateLinksHeaderValue({
|
|
8
|
+
internalTemplateName,
|
|
9
|
+
localizedPathnames,
|
|
10
|
+
request,
|
|
11
|
+
resolvedLocale,
|
|
12
|
+
routing
|
|
13
|
+
}) {
|
|
14
|
+
const normalizedUrl = request.nextUrl.clone();
|
|
15
|
+
const host = getHost(request.headers);
|
|
16
|
+
if (host) {
|
|
17
|
+
normalizedUrl.port = '';
|
|
18
|
+
normalizedUrl.host = host;
|
|
19
|
+
}
|
|
20
|
+
normalizedUrl.protocol = request.headers.get('x-forwarded-proto') ?? normalizedUrl.protocol;
|
|
21
|
+
normalizedUrl.pathname = getNormalizedPathname(normalizedUrl.pathname, routing.locales, routing.localePrefix);
|
|
22
|
+
function getAlternateEntry(url, locale) {
|
|
23
|
+
url.pathname = normalizeTrailingSlash(url.pathname);
|
|
24
|
+
if (request.nextUrl.basePath) {
|
|
25
|
+
url = new URL(url);
|
|
26
|
+
url.pathname = applyBasePath(url.pathname, request.nextUrl.basePath);
|
|
27
|
+
}
|
|
28
|
+
return `<${url.toString()}>; rel="alternate"; hreflang="${locale}"`;
|
|
29
|
+
}
|
|
30
|
+
function getLocalizedPathname(pathname, locale) {
|
|
31
|
+
if (localizedPathnames && typeof localizedPathnames === 'object') {
|
|
32
|
+
const sourceTemplate = localizedPathnames[resolvedLocale];
|
|
33
|
+
return formatTemplatePathname(pathname, sourceTemplate ?? internalTemplateName, localizedPathnames[locale] ?? internalTemplateName);
|
|
34
|
+
} else {
|
|
35
|
+
return pathname;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const links = getLocalePrefixes(routing.locales, routing.localePrefix, false).flatMap(([locale, prefix]) => {
|
|
39
|
+
function prefixPathname(pathname) {
|
|
40
|
+
if (pathname === '/') {
|
|
41
|
+
return prefix;
|
|
42
|
+
} else {
|
|
43
|
+
return prefix + pathname;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
let url;
|
|
47
|
+
if (routing.domains) {
|
|
48
|
+
const domainConfigs = routing.domains.filter(cur => isLocaleSupportedOnDomain(locale, cur));
|
|
49
|
+
return domainConfigs.map(domainConfig => {
|
|
50
|
+
url = new URL(normalizedUrl);
|
|
51
|
+
url.port = '';
|
|
52
|
+
url.host = domainConfig.domain;
|
|
53
|
+
|
|
54
|
+
// Important: Use `normalizedUrl` here, as `url` potentially uses
|
|
55
|
+
// a `basePath` that automatically gets applied to the pathname
|
|
56
|
+
url.pathname = getLocalizedPathname(normalizedUrl.pathname, locale);
|
|
57
|
+
if (locale !== domainConfig.defaultLocale || routing.localePrefix.mode === 'always') {
|
|
58
|
+
url.pathname = prefixPathname(url.pathname);
|
|
59
|
+
}
|
|
60
|
+
return getAlternateEntry(url, locale);
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
let pathname;
|
|
64
|
+
if (localizedPathnames && typeof localizedPathnames === 'object') {
|
|
65
|
+
pathname = getLocalizedPathname(normalizedUrl.pathname, locale);
|
|
66
|
+
} else {
|
|
67
|
+
pathname = normalizedUrl.pathname;
|
|
68
|
+
}
|
|
69
|
+
if (locale !== routing.defaultLocale || routing.localePrefix.mode === 'always') {
|
|
70
|
+
pathname = prefixPathname(pathname);
|
|
71
|
+
}
|
|
72
|
+
url = new URL(pathname, normalizedUrl);
|
|
73
|
+
}
|
|
74
|
+
return getAlternateEntry(url, locale);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Add x-default entry
|
|
78
|
+
const shouldAddXDefault =
|
|
79
|
+
// For domain-based routing there is no reasonable x-default
|
|
80
|
+
!routing.domains || routing.domains.length === 0;
|
|
81
|
+
if (shouldAddXDefault) {
|
|
82
|
+
const localizedPathname = getLocalizedPathname(normalizedUrl.pathname, routing.defaultLocale);
|
|
83
|
+
if (localizedPathname) {
|
|
84
|
+
const url = new URL(localizedPathname, normalizedUrl);
|
|
85
|
+
links.push(getAlternateEntry(url, 'x-default'));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return links.join(', ');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { getAlternateLinksHeaderValue as default };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { receiveRoutingConfig } from '../routing/config.js';
|
|
3
|
+
import { HEADER_LOCALE_NAME } from '../shared/constants.js';
|
|
4
|
+
import { matchesPathname, normalizeTrailingSlash, getLocalePrefix, getLocalizedTemplate } from '../shared/utils.js';
|
|
5
|
+
import getAlternateLinksHeaderValue from './getAlternateLinksHeaderValue.js';
|
|
6
|
+
import resolveLocale from './resolveLocale.js';
|
|
7
|
+
import syncCookie from './syncCookie.js';
|
|
8
|
+
import { sanitizePathname, isLocaleSupportedOnDomain, getNormalizedPathname, getPathnameMatch, getInternalTemplate, formatTemplatePathname, formatPathname, getBestMatchingDomain, applyBasePath, getLocaleAsPrefix } from './utils.js';
|
|
9
|
+
|
|
10
|
+
function createMiddleware(routing) {
|
|
11
|
+
const resolvedRouting = receiveRoutingConfig(routing);
|
|
12
|
+
return function middleware(request) {
|
|
13
|
+
let unsafeExternalPathname;
|
|
14
|
+
try {
|
|
15
|
+
// Resolve potential foreign symbols (e.g. /ja/%E7%B4%84 → /ja/約))
|
|
16
|
+
unsafeExternalPathname = decodeURI(request.nextUrl.pathname);
|
|
17
|
+
} catch {
|
|
18
|
+
// In case an invalid pathname is encountered, forward
|
|
19
|
+
// it to Next.js which in turn responds with a 400
|
|
20
|
+
return NextResponse.next();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Sanitize malicious URIs to prevent open redirect attacks due to
|
|
24
|
+
// decodeURI doesn't escape encoded backslashes ('%5C' & '%5c')
|
|
25
|
+
const externalPathname = sanitizePathname(unsafeExternalPathname);
|
|
26
|
+
const {
|
|
27
|
+
domain,
|
|
28
|
+
locale
|
|
29
|
+
} = resolveLocale(resolvedRouting, request.headers, request.cookies, externalPathname);
|
|
30
|
+
const hasMatchedDefaultLocale = domain ? domain.defaultLocale === locale : locale === resolvedRouting.defaultLocale;
|
|
31
|
+
const domainsConfig = resolvedRouting.domains?.filter(curDomain => isLocaleSupportedOnDomain(locale, curDomain)) || [];
|
|
32
|
+
const hasUnknownHost = resolvedRouting.domains != null && !domain;
|
|
33
|
+
function next(url) {
|
|
34
|
+
const urlObj = new URL(url, request.url);
|
|
35
|
+
if (request.nextUrl.basePath) {
|
|
36
|
+
urlObj.pathname = applyBasePath(urlObj.pathname, request.nextUrl.basePath);
|
|
37
|
+
}
|
|
38
|
+
const headers = new Headers(request.headers);
|
|
39
|
+
headers.set(HEADER_LOCALE_NAME, locale);
|
|
40
|
+
const isRewriteNecessary = normalizeTrailingSlash(request.nextUrl.pathname) !== normalizeTrailingSlash(urlObj.pathname);
|
|
41
|
+
if (isRewriteNecessary) {
|
|
42
|
+
return NextResponse.rewrite(urlObj, {
|
|
43
|
+
request: {
|
|
44
|
+
headers
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
return NextResponse.next({
|
|
49
|
+
request: {
|
|
50
|
+
headers
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function redirect(url, redirectDomain) {
|
|
56
|
+
const urlObj = new URL(url, request.url);
|
|
57
|
+
urlObj.pathname = normalizeTrailingSlash(urlObj.pathname);
|
|
58
|
+
if (domainsConfig.length > 0 && !redirectDomain && domain) {
|
|
59
|
+
const bestMatchingDomain = getBestMatchingDomain(domain, locale, domainsConfig);
|
|
60
|
+
if (bestMatchingDomain) {
|
|
61
|
+
redirectDomain = bestMatchingDomain.domain;
|
|
62
|
+
if (bestMatchingDomain.defaultLocale === locale && resolvedRouting.localePrefix.mode === 'as-needed') {
|
|
63
|
+
urlObj.pathname = getNormalizedPathname(urlObj.pathname, resolvedRouting.locales, resolvedRouting.localePrefix);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (redirectDomain) {
|
|
68
|
+
urlObj.host = redirectDomain;
|
|
69
|
+
if (request.headers.get('x-forwarded-host')) {
|
|
70
|
+
urlObj.protocol = request.headers.get('x-forwarded-proto') ?? request.nextUrl.protocol;
|
|
71
|
+
const redirectDomainPort = redirectDomain.split(':')[1];
|
|
72
|
+
urlObj.port = redirectDomainPort ?? request.headers.get('x-forwarded-port') ?? '';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (request.nextUrl.basePath) {
|
|
76
|
+
urlObj.pathname = applyBasePath(urlObj.pathname, request.nextUrl.basePath);
|
|
77
|
+
}
|
|
78
|
+
hasRedirected = true;
|
|
79
|
+
return NextResponse.redirect(urlObj.toString());
|
|
80
|
+
}
|
|
81
|
+
const unprefixedExternalPathname = getNormalizedPathname(externalPathname, resolvedRouting.locales, resolvedRouting.localePrefix);
|
|
82
|
+
const pathnameMatch = getPathnameMatch(externalPathname, resolvedRouting.locales, resolvedRouting.localePrefix, domain);
|
|
83
|
+
const hasLocalePrefix = pathnameMatch != null;
|
|
84
|
+
const isUnprefixedRouting = resolvedRouting.localePrefix.mode === 'never' || hasMatchedDefaultLocale && resolvedRouting.localePrefix.mode === 'as-needed';
|
|
85
|
+
let response;
|
|
86
|
+
let internalTemplateName;
|
|
87
|
+
let hasRedirected;
|
|
88
|
+
let unprefixedInternalPathname = unprefixedExternalPathname;
|
|
89
|
+
const pathnames = resolvedRouting.pathnames;
|
|
90
|
+
if (pathnames) {
|
|
91
|
+
let resolvedTemplateLocale;
|
|
92
|
+
[resolvedTemplateLocale, internalTemplateName] = getInternalTemplate(pathnames, unprefixedExternalPathname, locale);
|
|
93
|
+
if (internalTemplateName) {
|
|
94
|
+
const pathnameConfig = pathnames[internalTemplateName];
|
|
95
|
+
const localeTemplate = getLocalizedTemplate(pathnameConfig, locale, internalTemplateName);
|
|
96
|
+
if (matchesPathname(localeTemplate, unprefixedExternalPathname)) {
|
|
97
|
+
unprefixedInternalPathname = formatTemplatePathname(unprefixedExternalPathname, localeTemplate, internalTemplateName);
|
|
98
|
+
} else {
|
|
99
|
+
let sourceTemplate;
|
|
100
|
+
if (resolvedTemplateLocale) {
|
|
101
|
+
// A localized pathname from another locale has matched
|
|
102
|
+
sourceTemplate = getLocalizedTemplate(pathnameConfig, resolvedTemplateLocale, internalTemplateName);
|
|
103
|
+
} else {
|
|
104
|
+
// An internal pathname has matched that
|
|
105
|
+
// doesn't have a localized pathname
|
|
106
|
+
sourceTemplate = internalTemplateName;
|
|
107
|
+
}
|
|
108
|
+
const localePrefix = isUnprefixedRouting ? undefined : getLocalePrefix(locale, resolvedRouting.localePrefix);
|
|
109
|
+
const template = formatTemplatePathname(unprefixedExternalPathname, sourceTemplate, localeTemplate);
|
|
110
|
+
response = redirect(formatPathname(template, localePrefix, request.nextUrl.search));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!response) {
|
|
115
|
+
if (unprefixedInternalPathname === '/' && !hasLocalePrefix) {
|
|
116
|
+
if (isUnprefixedRouting) {
|
|
117
|
+
response = next(formatPathname(unprefixedInternalPathname, getLocaleAsPrefix(locale), request.nextUrl.search));
|
|
118
|
+
} else {
|
|
119
|
+
response = redirect(formatPathname(unprefixedExternalPathname, getLocalePrefix(locale, resolvedRouting.localePrefix), request.nextUrl.search));
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
const internalHref = formatPathname(unprefixedInternalPathname, getLocaleAsPrefix(locale), request.nextUrl.search);
|
|
123
|
+
if (hasLocalePrefix) {
|
|
124
|
+
const externalHref = formatPathname(unprefixedExternalPathname, pathnameMatch.prefix, request.nextUrl.search);
|
|
125
|
+
if (resolvedRouting.localePrefix.mode === 'never') {
|
|
126
|
+
response = redirect(formatPathname(unprefixedExternalPathname, undefined, request.nextUrl.search));
|
|
127
|
+
} else if (pathnameMatch.exact) {
|
|
128
|
+
if (hasMatchedDefaultLocale && isUnprefixedRouting) {
|
|
129
|
+
response = redirect(formatPathname(unprefixedExternalPathname, undefined, request.nextUrl.search));
|
|
130
|
+
} else {
|
|
131
|
+
if (resolvedRouting.domains) {
|
|
132
|
+
const pathDomain = getBestMatchingDomain(domain, pathnameMatch.locale, domainsConfig);
|
|
133
|
+
if (domain?.domain !== pathDomain?.domain && !hasUnknownHost) {
|
|
134
|
+
response = redirect(externalHref, pathDomain?.domain);
|
|
135
|
+
} else {
|
|
136
|
+
response = next(internalHref);
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
response = next(internalHref);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
response = redirect(externalHref);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
if (isUnprefixedRouting) {
|
|
147
|
+
response = next(internalHref);
|
|
148
|
+
} else {
|
|
149
|
+
response = redirect(formatPathname(unprefixedExternalPathname, getLocalePrefix(locale, resolvedRouting.localePrefix), request.nextUrl.search));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
syncCookie(request, response, locale, resolvedRouting, domain);
|
|
155
|
+
if (!hasRedirected && resolvedRouting.localePrefix.mode !== 'never' && resolvedRouting.alternateLinks && resolvedRouting.locales.length > 1) {
|
|
156
|
+
response.headers.set('Link', getAlternateLinksHeaderValue({
|
|
157
|
+
routing: resolvedRouting,
|
|
158
|
+
internalTemplateName,
|
|
159
|
+
localizedPathnames: internalTemplateName != null && pathnames ? pathnames[internalTemplateName] : undefined,
|
|
160
|
+
request,
|
|
161
|
+
resolvedLocale: locale
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
return response;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export { createMiddleware as default };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { match } from '@formatjs/intl-localematcher';
|
|
2
|
+
import Negotiator from 'negotiator';
|
|
3
|
+
import { getPathnameMatch, isLocaleSupportedOnDomain, getHost } from './utils.js';
|
|
4
|
+
|
|
5
|
+
function findDomainFromHost(requestHeaders, domains) {
|
|
6
|
+
const host = getHost(requestHeaders);
|
|
7
|
+
if (host) {
|
|
8
|
+
return domains.find(cur => cur.domain === host);
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
function orderLocales(locales) {
|
|
13
|
+
// Workaround for https://github.com/formatjs/formatjs/issues/4469
|
|
14
|
+
return locales.slice().sort((a, b) => b.length - a.length);
|
|
15
|
+
}
|
|
16
|
+
function mapToProvidedLocale(locales, locale) {
|
|
17
|
+
return locales.find(cur => cur.toLowerCase() === locale.toLowerCase());
|
|
18
|
+
}
|
|
19
|
+
function getAcceptLanguageLocale(requestHeaders, locales, defaultLocale) {
|
|
20
|
+
let locale;
|
|
21
|
+
const languages = new Negotiator({
|
|
22
|
+
headers: {
|
|
23
|
+
'accept-language': requestHeaders.get('accept-language') || undefined
|
|
24
|
+
}
|
|
25
|
+
}).languages();
|
|
26
|
+
try {
|
|
27
|
+
const orderedLocales = orderLocales(locales);
|
|
28
|
+
locale = mapToProvidedLocale(locales, match(languages, orderedLocales, defaultLocale));
|
|
29
|
+
} catch {
|
|
30
|
+
// Invalid language
|
|
31
|
+
}
|
|
32
|
+
return locale;
|
|
33
|
+
}
|
|
34
|
+
function getLocaleFromCookie(routing, requestCookies) {
|
|
35
|
+
if (routing.localeCookie && requestCookies.has(routing.localeCookie.name)) {
|
|
36
|
+
const value = requestCookies.get(routing.localeCookie.name)?.value;
|
|
37
|
+
if (value && routing.locales.includes(value)) {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function resolveLocaleFromPrefix(routing, requestHeaders, requestCookies, pathname) {
|
|
43
|
+
let locale;
|
|
44
|
+
|
|
45
|
+
// Prio 1: Use route prefix
|
|
46
|
+
if (pathname) {
|
|
47
|
+
locale = getPathnameMatch(pathname, routing.locales, routing.localePrefix)?.locale;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Prio 2: Use existing cookie
|
|
51
|
+
if (!locale && routing.localeDetection) {
|
|
52
|
+
locale = getLocaleFromCookie(routing, requestCookies);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Prio 3: Use the `accept-language` header
|
|
56
|
+
if (!locale && routing.localeDetection) {
|
|
57
|
+
locale = getAcceptLanguageLocale(requestHeaders, routing.locales, routing.defaultLocale);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Prio 4: Use default locale
|
|
61
|
+
if (!locale) {
|
|
62
|
+
locale = routing.defaultLocale;
|
|
63
|
+
}
|
|
64
|
+
return locale;
|
|
65
|
+
}
|
|
66
|
+
function resolveLocaleFromDomain(routing, requestHeaders, requestCookies, pathname) {
|
|
67
|
+
const domains = routing.domains;
|
|
68
|
+
const domain = findDomainFromHost(requestHeaders, domains);
|
|
69
|
+
if (!domain) {
|
|
70
|
+
return {
|
|
71
|
+
locale: resolveLocaleFromPrefix(routing, requestHeaders, requestCookies, pathname)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
let locale;
|
|
75
|
+
|
|
76
|
+
// Prio 1: Use route prefix
|
|
77
|
+
if (pathname) {
|
|
78
|
+
const prefixLocale = getPathnameMatch(pathname, routing.locales, routing.localePrefix, domain)?.locale;
|
|
79
|
+
if (prefixLocale) {
|
|
80
|
+
if (isLocaleSupportedOnDomain(prefixLocale, domain)) {
|
|
81
|
+
locale = prefixLocale;
|
|
82
|
+
} else {
|
|
83
|
+
// Causes a redirect to a domain that supports the locale
|
|
84
|
+
return {
|
|
85
|
+
locale: prefixLocale,
|
|
86
|
+
domain
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Prio 2: Use existing cookie
|
|
93
|
+
if (!locale && routing.localeDetection) {
|
|
94
|
+
const cookieLocale = getLocaleFromCookie(routing, requestCookies);
|
|
95
|
+
if (cookieLocale) {
|
|
96
|
+
if (isLocaleSupportedOnDomain(cookieLocale, domain)) {
|
|
97
|
+
locale = cookieLocale;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Prio 3: Use the `accept-language` header
|
|
103
|
+
if (!locale && routing.localeDetection) {
|
|
104
|
+
const headerLocale = getAcceptLanguageLocale(requestHeaders, domain.locales, domain.defaultLocale);
|
|
105
|
+
if (headerLocale) {
|
|
106
|
+
locale = headerLocale;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Prio 4: Use default locale
|
|
111
|
+
if (!locale) {
|
|
112
|
+
locale = domain.defaultLocale;
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
locale,
|
|
116
|
+
domain
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function resolveLocale(routing, requestHeaders, requestCookies, pathname) {
|
|
120
|
+
if (routing.domains) {
|
|
121
|
+
return resolveLocaleFromDomain(routing, requestHeaders, requestCookies, pathname);
|
|
122
|
+
} else {
|
|
123
|
+
return {
|
|
124
|
+
locale: resolveLocaleFromPrefix(routing, requestHeaders, requestCookies, pathname)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { resolveLocale as default, getAcceptLanguageLocale };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getAcceptLanguageLocale } from './resolveLocale.js';
|
|
2
|
+
|
|
3
|
+
function syncCookie(request, response, locale, routing, domain) {
|
|
4
|
+
if (!routing.localeCookie) return;
|
|
5
|
+
const {
|
|
6
|
+
name,
|
|
7
|
+
...rest
|
|
8
|
+
} = routing.localeCookie;
|
|
9
|
+
const hasLocaleCookie = request.cookies.has(name);
|
|
10
|
+
const hasOutdatedCookie = hasLocaleCookie && request.cookies.get(name)?.value !== locale;
|
|
11
|
+
if (hasOutdatedCookie) {
|
|
12
|
+
response.cookies.set(name, locale, {
|
|
13
|
+
path: request.nextUrl.basePath || undefined,
|
|
14
|
+
...rest
|
|
15
|
+
});
|
|
16
|
+
} else if (!hasLocaleCookie) {
|
|
17
|
+
const acceptLanguageLocale = getAcceptLanguageLocale(request.headers, domain?.locales || routing.locales, routing.defaultLocale);
|
|
18
|
+
if (acceptLanguageLocale !== locale) {
|
|
19
|
+
response.cookies.set(name, locale, {
|
|
20
|
+
path: request.nextUrl.basePath || undefined,
|
|
21
|
+
...rest
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { syncCookie as default };
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { normalizeTrailingSlash, getSortedPathnames, matchesPathname, prefixPathname, getLocalePrefix, templateToRegex, getLocalizedTemplate } from '../shared/utils.js';
|
|
2
|
+
|
|
3
|
+
function getInternalTemplate(pathnames, pathname, locale) {
|
|
4
|
+
const sortedPathnames = getSortedPathnames(Object.keys(pathnames));
|
|
5
|
+
|
|
6
|
+
// Try to find a localized pathname that matches
|
|
7
|
+
for (const internalPathname of sortedPathnames) {
|
|
8
|
+
const localizedPathnamesOrPathname = pathnames[internalPathname];
|
|
9
|
+
if (typeof localizedPathnamesOrPathname === 'string') {
|
|
10
|
+
const localizedPathname = localizedPathnamesOrPathname;
|
|
11
|
+
if (matchesPathname(localizedPathname, pathname)) {
|
|
12
|
+
return [undefined, internalPathname];
|
|
13
|
+
}
|
|
14
|
+
} else {
|
|
15
|
+
// Prefer the entry with the current locale in case multiple
|
|
16
|
+
// localized pathnames match the current pathname
|
|
17
|
+
const sortedEntries = Object.entries(localizedPathnamesOrPathname);
|
|
18
|
+
const curLocaleIndex = sortedEntries.findIndex(([entryLocale]) => entryLocale === locale);
|
|
19
|
+
if (curLocaleIndex > 0) {
|
|
20
|
+
sortedEntries.unshift(sortedEntries.splice(curLocaleIndex, 1)[0]);
|
|
21
|
+
}
|
|
22
|
+
for (const [entryLocale] of sortedEntries) {
|
|
23
|
+
const localizedTemplate = getLocalizedTemplate(pathnames[internalPathname], entryLocale, internalPathname);
|
|
24
|
+
if (matchesPathname(localizedTemplate, pathname)) {
|
|
25
|
+
return [entryLocale, internalPathname];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Try to find an internal pathname that matches (this can be the case
|
|
32
|
+
// if all localized pathnames are different from the internal pathnames)
|
|
33
|
+
for (const internalPathname of Object.keys(pathnames)) {
|
|
34
|
+
if (matchesPathname(internalPathname, pathname)) {
|
|
35
|
+
return [undefined, internalPathname];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// No match
|
|
40
|
+
return [undefined, undefined];
|
|
41
|
+
}
|
|
42
|
+
function formatTemplatePathname(sourcePathname, sourceTemplate, targetTemplate, prefix) {
|
|
43
|
+
const params = getRouteParams(sourceTemplate, sourcePathname);
|
|
44
|
+
let targetPathname = '';
|
|
45
|
+
targetPathname += formatPathnameTemplate(targetTemplate, params);
|
|
46
|
+
|
|
47
|
+
// A pathname with an optional catchall like `/categories/[[...slug]]`
|
|
48
|
+
// should be normalized to `/categories` if the catchall is not present
|
|
49
|
+
// and no trailing slash is configured
|
|
50
|
+
targetPathname = normalizeTrailingSlash(targetPathname);
|
|
51
|
+
return targetPathname;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Removes potential prefixes from the pathname.
|
|
56
|
+
*/
|
|
57
|
+
function getNormalizedPathname(pathname, locales, localePrefix) {
|
|
58
|
+
// Add trailing slash for consistent handling
|
|
59
|
+
// both for the root as well as nested paths
|
|
60
|
+
if (!pathname.endsWith('/')) {
|
|
61
|
+
pathname += '/';
|
|
62
|
+
}
|
|
63
|
+
const localePrefixes = getLocalePrefixes(locales, localePrefix);
|
|
64
|
+
const regex = new RegExp(`^(${localePrefixes.map(([, prefix]) => prefix.replaceAll('/', '\\/')).join('|')})/(.*)`, 'i');
|
|
65
|
+
const match = pathname.match(regex);
|
|
66
|
+
let result = match ? '/' + match[2] : pathname;
|
|
67
|
+
if (result !== '/') {
|
|
68
|
+
result = normalizeTrailingSlash(result);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
function getLocalePrefixes(locales, localePrefix, sort = true) {
|
|
73
|
+
const prefixes = locales.map(locale => [locale, getLocalePrefix(locale, localePrefix)]);
|
|
74
|
+
if (sort) {
|
|
75
|
+
// More specific ones first
|
|
76
|
+
prefixes.sort((a, b) => b[1].length - a[1].length);
|
|
77
|
+
}
|
|
78
|
+
return prefixes;
|
|
79
|
+
}
|
|
80
|
+
function getPathnameMatch(pathname, locales, localePrefix, domain) {
|
|
81
|
+
const localePrefixes = getLocalePrefixes(locales, localePrefix);
|
|
82
|
+
|
|
83
|
+
// Sort to prioritize domain locales
|
|
84
|
+
if (domain) {
|
|
85
|
+
localePrefixes.sort(([localeA], [localeB]) => {
|
|
86
|
+
if (localeA === domain.defaultLocale) return -1;
|
|
87
|
+
if (localeB === domain.defaultLocale) return 1;
|
|
88
|
+
const isLocaleAInDomain = domain.locales.includes(localeA);
|
|
89
|
+
const isLocaleBInDomain = domain.locales.includes(localeB);
|
|
90
|
+
if (isLocaleAInDomain && !isLocaleBInDomain) return -1;
|
|
91
|
+
if (!isLocaleAInDomain && isLocaleBInDomain) return 1;
|
|
92
|
+
return 0;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
for (const [locale, prefix] of localePrefixes) {
|
|
96
|
+
let exact, matches;
|
|
97
|
+
if (pathname === prefix || pathname.startsWith(prefix + '/')) {
|
|
98
|
+
exact = matches = true;
|
|
99
|
+
} else {
|
|
100
|
+
const normalizedPathname = pathname.toLowerCase();
|
|
101
|
+
const normalizedPrefix = prefix.toLowerCase();
|
|
102
|
+
if (normalizedPathname === normalizedPrefix || normalizedPathname.startsWith(normalizedPrefix + '/')) {
|
|
103
|
+
exact = false;
|
|
104
|
+
matches = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (matches) {
|
|
108
|
+
return {
|
|
109
|
+
locale,
|
|
110
|
+
prefix,
|
|
111
|
+
matchedPrefix: pathname.slice(0, prefix.length),
|
|
112
|
+
exact
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function getRouteParams(template, pathname) {
|
|
118
|
+
const normalizedPathname = normalizeTrailingSlash(pathname);
|
|
119
|
+
const normalizedTemplate = normalizeTrailingSlash(template);
|
|
120
|
+
const regex = templateToRegex(normalizedTemplate);
|
|
121
|
+
const match = regex.exec(normalizedPathname);
|
|
122
|
+
if (!match) return undefined;
|
|
123
|
+
const params = {};
|
|
124
|
+
const keys = normalizedTemplate.match(/\[([^\]]+)\]/g) ?? [];
|
|
125
|
+
for (let i = 1; i < match.length; i++) {
|
|
126
|
+
const rawKey = keys[i - 1];
|
|
127
|
+
if (!rawKey) continue;
|
|
128
|
+
const key = rawKey.replace(/[[\]]/g, '');
|
|
129
|
+
const value = match[i] ?? '';
|
|
130
|
+
params[key] = value;
|
|
131
|
+
}
|
|
132
|
+
return params;
|
|
133
|
+
}
|
|
134
|
+
function formatPathnameTemplate(template, params) {
|
|
135
|
+
if (!params) return template;
|
|
136
|
+
|
|
137
|
+
// Simplify syntax for optional catchall ('[[...slug]]') so
|
|
138
|
+
// we can replace the value with simple interpolation
|
|
139
|
+
template = template.replace(/\[\[/g, '[').replace(/\]\]/g, ']');
|
|
140
|
+
let result = template;
|
|
141
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
142
|
+
result = result.replace(`[${key}]`, value);
|
|
143
|
+
});
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
function formatPathname(pathname, prefix, search) {
|
|
147
|
+
let result = pathname;
|
|
148
|
+
if (prefix) {
|
|
149
|
+
result = prefixPathname(prefix, result);
|
|
150
|
+
}
|
|
151
|
+
if (search) {
|
|
152
|
+
result += search;
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
function getHost(requestHeaders) {
|
|
157
|
+
return requestHeaders.get('x-forwarded-host') ?? requestHeaders.get('host') ?? undefined;
|
|
158
|
+
}
|
|
159
|
+
function isLocaleSupportedOnDomain(locale, domain) {
|
|
160
|
+
return domain.defaultLocale === locale || domain.locales.includes(locale);
|
|
161
|
+
}
|
|
162
|
+
function getBestMatchingDomain(curHostDomain, locale, domainsConfig) {
|
|
163
|
+
let domainConfig;
|
|
164
|
+
|
|
165
|
+
// Prio 1: Stay on current domain
|
|
166
|
+
if (curHostDomain && isLocaleSupportedOnDomain(locale, curHostDomain)) {
|
|
167
|
+
domainConfig = curHostDomain;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Prio 2: Use alternative domain with matching default locale
|
|
171
|
+
if (!domainConfig) {
|
|
172
|
+
domainConfig = domainsConfig.find(cur => cur.defaultLocale === locale);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Prio 3: Use alternative domain that supports the locale
|
|
176
|
+
if (!domainConfig) {
|
|
177
|
+
domainConfig = domainsConfig.find(cur => cur.locales.includes(locale));
|
|
178
|
+
}
|
|
179
|
+
return domainConfig;
|
|
180
|
+
}
|
|
181
|
+
function applyBasePath(pathname, basePath) {
|
|
182
|
+
return normalizeTrailingSlash(basePath + pathname);
|
|
183
|
+
}
|
|
184
|
+
function getLocaleAsPrefix(locale) {
|
|
185
|
+
return `/${locale}`;
|
|
186
|
+
}
|
|
187
|
+
function sanitizePathname(pathname) {
|
|
188
|
+
// Sanitize malicious URIs, e.g.:
|
|
189
|
+
// '/en/\\example.org → /en/%5C%5Cexample.org'
|
|
190
|
+
// '/en////example.org → /en/example.org'
|
|
191
|
+
return pathname.replace(/\\/g, '%5C').replace(/\/+/g, '/');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export { applyBasePath, formatPathname, formatPathnameTemplate, formatTemplatePathname, getBestMatchingDomain, getHost, getInternalTemplate, getLocaleAsPrefix, getLocalePrefixes, getNormalizedPathname, getPathnameMatch, getRouteParams, isLocaleSupportedOnDomain, sanitizePathname };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './middleware/middleware.js';
|