diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index 90cbed8222be..2fd86527ac5c 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -1168,6 +1168,18 @@ export const MissingMiddlewareForInternationalization = { "Your configuration setting `i18n.routing: 'manual'` requires you to provide your own i18n `middleware` file.", } satisfies ErrorData; +/** + * @docs + * @description + * An invalid i18n middleware configuration was detected. + */ +export const InvalidI18nMiddlewareConfiguration = { + name: 'InvalidI18nMiddlewareConfiguration', + title: 'Invalid internationalization middleware configuration', + message: + 'The option `redirectToDefaultLocale` can be enabled only when `prefixDefaultLocale` is also set to `true`, otherwise redirects might cause infinite loops. Enable the option `prefixDefaultLocale` to continue to use `redirectToDefaultLocale`, or ensure both are set to `false`.', +} satisfies ErrorData; + /** * @docs * @description diff --git a/packages/astro/src/virtual-modules/i18n.ts b/packages/astro/src/virtual-modules/i18n.ts index 1b611f6237aa..48e714dfd5ca 100644 --- a/packages/astro/src/virtual-modules/i18n.ts +++ b/packages/astro/src/virtual-modules/i18n.ts @@ -3,7 +3,10 @@ import * as config from 'astro:config/server'; import { toFallbackType } from '../core/app/common.js'; import { toRoutingStrategy } from '../core/app/index.js'; import type { SSRManifest } from '../core/app/types.js'; -import { IncorrectStrategyForI18n } from '../core/errors/errors-data.js'; +import { + IncorrectStrategyForI18n, + InvalidI18nMiddlewareConfiguration, +} from '../core/errors/errors-data.js'; import { AstroError } from '../core/errors/index.js'; import type { RedirectToFallback } from '../i18n/index.js'; import * as I18nInternals from '../i18n/index.js'; @@ -339,7 +342,19 @@ if (i18n?.routing === 'manual') { } type OnlyObject = T extends object ? T : never; -type NewAstroRoutingConfigWithoutManual = OnlyObject['routing']>; + +export type I18nMiddlewareOptions = { + fallbackType: OnlyObject['routing']>['fallbackType']; +} & ( + | { + prefixDefaultLocale: false; + redirectToDefaultLocale: false; + } + | { + prefixDefaultLocale: true; + redirectToDefaultLocale: boolean; + } +); /** * @param {AstroConfig['i18n']['routing']} customOptions @@ -371,10 +386,17 @@ type NewAstroRoutingConfigWithoutManual = OnlyObject MiddlewareHandler; +export let middleware: (customOptions: I18nMiddlewareOptions) => MiddlewareHandler; if (i18n?.routing === 'manual') { - middleware = (customOptions: NewAstroRoutingConfigWithoutManual) => { + middleware = (customOptions) => { + if ( + customOptions.prefixDefaultLocale === false && + // @ts-expect-error types do not allow this but we also check at runtime + customOptions.redirectToDefaultLocale === true + ) { + throw new AstroError(InvalidI18nMiddlewareConfiguration); + } strategy = toRoutingStrategy(customOptions, {}); fallbackType = toFallbackType(customOptions); const manifest: SSRManifest['i18n'] = { diff --git a/packages/astro/test/types/astro-i18n-virtual-module.ts b/packages/astro/test/types/astro-i18n-virtual-module.ts new file mode 100644 index 000000000000..13afc1dae2da --- /dev/null +++ b/packages/astro/test/types/astro-i18n-virtual-module.ts @@ -0,0 +1,29 @@ +import { describe, it } from 'node:test'; +import '../../client.d.ts'; +import type { I18nMiddlewareOptions } from 'astro:i18n'; + +describe('astro:i18n', () => { + it('middleware', () => { + ({ + fallbackType: 'rewrite', + prefixDefaultLocale: false, + redirectToDefaultLocale: false, + }) satisfies I18nMiddlewareOptions; + ({ + fallbackType: 'rewrite', + prefixDefaultLocale: false, + redirectToDefaultLocale: true, + // @ts-expect-error invalid combination + }) satisfies I18nMiddlewareOptions; + ({ + fallbackType: 'rewrite', + prefixDefaultLocale: true, + redirectToDefaultLocale: false, + }) satisfies I18nMiddlewareOptions; + ({ + fallbackType: 'rewrite', + prefixDefaultLocale: true, + redirectToDefaultLocale: true, + }) satisfies I18nMiddlewareOptions; + }); +});