Repository: withastro/astro
File: packages/astro/src/i18n/index.ts (TypeScript)

Code before:

			if (urlLocale && fallbackKeys.includes(urlLocale)) {
				const fallbackLocale = fallback[urlLocale];
				// the user might have configured the locale using the granular locales, so we want to retrieve its corresponding path instead
				const pathFallbackLocale = getPathByLocale(fallbackLocale, locales);
				let newPathname: string;
				// If a locale falls back to the default locale, we want to **remove** the locale because
				// the default locale doesn't have a prefix
				if (pathFallbackLocale === defaultLocale && strategy === 'pathname-prefix-other-locales') {
					if (context.url.pathname.includes(`${base}`)) {
						newPathname = context.url.pathname.replace(`/${urlLocale}`, ``);
						// Ensure the pathname are non-empty. Redirects like "/fr" => "" may create infinite loops,
						// as the "Location" response header is empty.
						if (newPathname === '') {
							newPathname = '/';
						}
					} else {
						newPathname = context.url.pathname.replace(`/${urlLocale}`, `/`);
					}
				} else {
					newPathname = context.url.pathname.replace(`/${urlLocale}`, `/${pathFallbackLocale}`);
				}

				if (fallbackType === 'rewrite') {
					return await context.rewrite(newPathname + context.url.search);
				} else {
					return context.redirect(newPathname + context.url.search);
				}
			}
		}
		return response;
	};
}

// NOTE: public function exported to the users via `astro:i18n` module
export function createMiddleware(
	i18nManifest: SSRManifest['i18n'],
	base: SSRManifest['base'],
	trailingSlash: SSRManifest['trailingSlash'],
	format: SSRManifest['buildFormat'],
) {
	return createI18nMiddleware(i18nManifest, base, trailingSlash, format);
}


Review comment (bug):
The comment is still unclear. For example, you don't explain **why** a redirect from `/fr` to `/` could create an infinite loop.

By the way, I believe this isn't the right fix, or to be more precise, the bug is at line 387 where we use the `includes`. Maybe we should check if `base == "/"`, and if so, at line 388 we shouldn't remove the slash from the replace. What do you think? 

Code after:
			const segments = context.url.pathname.split('/');
			const urlLocale = segments.find((segment) => {
				for (const locale of locales) {
					if (typeof locale === 'string') {
						if (locale === segment) {
							return true;
						}
					} else if (locale.path === segment) {
						return true;
					}
				}
				return false;
			});

			if (urlLocale && fallbackKeys.includes(urlLocale)) {
				const fallbackLocale = fallback[urlLocale];
				// the user might have configured the locale using the granular locales, so we want to retrieve its corresponding path instead
				const pathFallbackLocale = getPathByLocale(fallbackLocale, locales);
				let newPathname: string;
				// If a locale falls back to the default locale, we want to **remove** the locale because
				// the default locale doesn't have a prefix
				if (pathFallbackLocale === defaultLocale && strategy === 'pathname-prefix-other-locales') {
					if (context.url.pathname.includes(`${base}`)) {
						newPathname = context.url.pathname.replace(`/${urlLocale}`, ``);
						// Ensure the pathname are non-empty. Redirects like "/fr" => "" may create infinite loops,
						// as the "Location" response header is empty.
						if (newPathname === '') {
							newPathname = '/';
						}
					} else {
						newPathname = context.url.pathname.replace(`/${urlLocale}`, `/`);
					}
				} else {
					newPathname = context.url.pathname.replace(`/${urlLocale}`, `/${pathFallbackLocale}`);
				}

				if (fallbackType === 'rewrite') {
					return await context.rewrite(newPathname + context.url.search);
				} else {
					return context.redirect(newPathname + context.url.search);
				}
			}
		}
		return response;
	};
}

// NOTE: public function exported to the users via `astro:i18n` module
export function createMiddleware(
	i18nManifest: SSRManifest['i18n'],
	base: SSRManifest['base'],