diff --git a/docs/pages/docs/routing.mdx b/docs/pages/docs/routing.mdx index f9a92e8e3..9a710c96c 100644 --- a/docs/pages/docs/routing.mdx +++ b/docs/pages/docs/routing.mdx @@ -321,6 +321,8 @@ If you want to serve your localized content based on different domains, you can - `ca.example.com/fr` ```tsx filename="config.ts" +import {DomainsConfig} from 'next-intl/routing'; + export const locales = ['en', 'fr'] as const; export const domains: DomainsConfig = [ @@ -342,4 +344,4 @@ export const domains: DomainsConfig = [ **Note that:** 1. You can optionally remove the locale prefix in pathnames by changing the [`localePrefix`](#locale-prefix) setting. -2. If no domain matches, the middleware will fall back to the [`defaultLocale`](/docs/routing/middleware#default-locale) (e.g. on `localhost`). \ No newline at end of file +2. If no domain matches, the middleware will fall back to the [`defaultLocale`](/docs/routing/middleware#default-locale) (e.g. on `localhost`). diff --git a/packages/next-intl/src/middleware/middleware.tsx b/packages/next-intl/src/middleware/middleware.tsx index 152d9beec..14a6a411e 100644 --- a/packages/next-intl/src/middleware/middleware.tsx +++ b/packages/next-intl/src/middleware/middleware.tsx @@ -40,7 +40,7 @@ export default function createMiddleware< ? domain.defaultLocale === locale : locale === config.defaultLocale; - const domainConfigs = + const domainsConfig = config.domains?.filter((curDomain) => isLocaleSupportedOnDomain(locale, curDomain) ) || []; @@ -64,11 +64,11 @@ export default function createMiddleware< function redirect(url: string, redirectDomain?: string) { const urlObj = new URL(normalizeTrailingSlash(url), request.url); - if (domainConfigs.length > 0 && !redirectDomain) { + if (domainsConfig.length > 0 && !redirectDomain) { const bestMatchingDomain = getBestMatchingDomain( domain, locale, - domainConfigs + domainsConfig ); if (bestMatchingDomain) { redirectDomain = bestMatchingDomain.domain; @@ -237,7 +237,7 @@ export default function createMiddleware< const pathDomain = getBestMatchingDomain( domain, pathnameMatch.locale, - domainConfigs + domainsConfig ); if (domain?.domain !== pathDomain?.domain && !hasUnknownHost) { diff --git a/packages/next-intl/src/middleware/resolveLocale.tsx b/packages/next-intl/src/middleware/resolveLocale.tsx index bcd0e14c9..43a50650d 100644 --- a/packages/next-intl/src/middleware/resolveLocale.tsx +++ b/packages/next-intl/src/middleware/resolveLocale.tsx @@ -1,14 +1,19 @@ import {match} from '@formatjs/intl-localematcher'; import Negotiator from 'negotiator'; import {RequestCookies} from 'next/dist/server/web/spec-extension/cookies'; -import {Locales, DomainConfig, Pathnames} from '../routing/types'; +import { + Locales, + Pathnames, + DomainsConfig, + DomainConfig +} from '../routing/types'; import {COOKIE_LOCALE_NAME} from '../shared/constants'; import {MiddlewareRoutingConfig} from './config'; import {getHost, getPathnameMatch, isLocaleSupportedOnDomain} from './utils'; function findDomainFromHost( requestHeaders: Headers, - domains: Array> + domains: DomainsConfig ) { let host = getHost(requestHeaders); diff --git a/packages/next-intl/src/middleware/utils.tsx b/packages/next-intl/src/middleware/utils.tsx index 2ac28b46c..1e15e8a43 100644 --- a/packages/next-intl/src/middleware/utils.tsx +++ b/packages/next-intl/src/middleware/utils.tsx @@ -2,7 +2,8 @@ import { Locales, LocalePrefixConfigVerbose, DomainConfig, - Pathnames + Pathnames, + DomainsConfig } from '../routing/types'; import { getLocalePrefix, @@ -238,7 +239,7 @@ export function isLocaleSupportedOnDomain( export function getBestMatchingDomain( curHostDomain: DomainConfig | undefined, locale: string, - domainConfigs: Array> + domainsConfig: DomainsConfig ) { let domainConfig; @@ -249,12 +250,12 @@ export function getBestMatchingDomain( // Prio 2: Use alternative domain with matching default locale if (!domainConfig) { - domainConfig = domainConfigs.find((cur) => cur.defaultLocale === locale); + domainConfig = domainsConfig.find((cur) => cur.defaultLocale === locale); } // Prio 3: Use alternative domain with restricted matching locale if (!domainConfig) { - domainConfig = domainConfigs.find( + domainConfig = domainsConfig.find( (cur) => cur.locales != null && cur.locales.includes(locale) ); } @@ -266,7 +267,7 @@ export function getBestMatchingDomain( // Prio 5: Use alternative domain that supports all locales if (!domainConfig) { - domainConfig = domainConfigs.find((cur) => !cur.locales); + domainConfig = domainsConfig.find((cur) => !cur.locales); } return domainConfig; diff --git a/packages/next-intl/src/routing/config.tsx b/packages/next-intl/src/routing/config.tsx index 785601551..6065c2bf9 100644 --- a/packages/next-intl/src/routing/config.tsx +++ b/packages/next-intl/src/routing/config.tsx @@ -1,8 +1,8 @@ import { Locales, - DomainConfig, LocalePrefix, - LocalePrefixConfigVerbose + LocalePrefixConfigVerbose, + DomainsConfig } from './types'; /** @@ -15,7 +15,7 @@ export type RoutingBaseConfigInput = { /** @see https://next-intl-docs.vercel.app/docs/routing#locale-prefix */ localePrefix?: LocalePrefix; /** Can be used to change the locale handling per domain. */ - domains?: Array>; + domains?: DomainsConfig; }; export function receiveLocalePrefixConfig( diff --git a/packages/next-intl/src/routing/index.tsx b/packages/next-intl/src/routing/index.tsx index 7b9234b9b..7de0af6d3 100644 --- a/packages/next-intl/src/routing/index.tsx +++ b/packages/next-intl/src/routing/index.tsx @@ -1,2 +1 @@ -export type {LocalePrefix} from './types'; -export type {Pathnames} from './types'; +export type {Pathnames, LocalePrefix, DomainsConfig} from './types'; diff --git a/packages/next-intl/src/routing/types.tsx b/packages/next-intl/src/routing/types.tsx index 84de6f539..4e9b16368 100644 --- a/packages/next-intl/src/routing/types.tsx +++ b/packages/next-intl/src/routing/types.tsx @@ -38,5 +38,9 @@ export type DomainConfig = { domain: string; /** Optionally restrict which locales are available on this domain. */ - locales?: AppLocales; + locales?: Array; }; + +export type DomainsConfig = Array< + DomainConfig +>; diff --git a/packages/next-intl/test/routing/types.test.tsx b/packages/next-intl/test/routing/types.test.tsx index c0cd2b5f7..7c71023f4 100644 --- a/packages/next-intl/test/routing/types.test.tsx +++ b/packages/next-intl/test/routing/types.test.tsx @@ -1,40 +1,69 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import {it} from 'vitest'; -import {LocalePrefix} from '../../src/routing/types'; +import {describe, it} from 'vitest'; +import {LocalePrefix, DomainConfig} from '../../src/routing/types'; -it('does not require a type param for simple values', () => { - const config: LocalePrefix = 'always'; -}); +describe('LocalePrefix', () => { + it('does not require a type param for simple values', () => { + const config: LocalePrefix = 'always'; + }); -it('provides strict typing for locales', () => { - const locales = ['en', 'de'] as const; - const config: LocalePrefix = { - mode: 'always', - prefixes: { - en: '/en', - // @ts-expect-error - unknown: '/unknown' - } - }; -}); + it('provides strict typing for locales', () => { + const locales = ['en', 'de'] as const; + const config: LocalePrefix = { + mode: 'always', + prefixes: { + en: '/en', + // @ts-expect-error + unknown: '/unknown' + } + }; + }); + + it('allows partial config', () => { + const locales = ['en', 'de'] as const; + const config: LocalePrefix = { + mode: 'always', + prefixes: { + en: '/en' + } + }; + }); -it('allows partial config', () => { - const locales = ['en', 'de'] as const; - const config: LocalePrefix = { - mode: 'always', - prefixes: { - en: '/en' - } - }; + it('provides optional typing for locales in prefixes', () => { + const config: LocalePrefix = { + mode: 'always', + prefixes: { + de: '/de', + en: '/en', + unknown: '/unknown' + } + }; + }); }); -it('provides optional typing for locales in prefixes', () => { - const config: LocalePrefix = { - mode: 'always', - prefixes: { - de: '/de', - en: '/en', - unknown: '/unknown' - } - }; +describe('DomainConfig', () => { + it('allows to handle all locales', () => { + const config: DomainConfig<['en', 'de']> = { + defaultLocale: 'en', + domain: 'example.com' + }; + }); + + it('allows to restrict locales', () => { + const config: DomainConfig<['en', 'de']> = { + defaultLocale: 'en', + domain: 'example.com', + locales: ['en'] + }; + }); + + it('errors for unknown locales', () => { + const config: DomainConfig<['en', 'de']> = { + // @ts-expect-error + defaultLocale: 'unknown', + domain: 'example.com', + // @ts-expect-error + locales: ['unknown'] + }; + }); });