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

Code before:
import { appendForwardSlash, joinPaths } from '@astrojs/internal-helpers/path';
import type { SSRManifest } from '../core/app/types.js';
import { shouldAppendForwardSlash } from '../core/build/util.js';
import { REROUTE_DIRECTIVE_HEADER } from '../core/constants.js';
import { i18nNoLocaleFoundInPath, MissingLocale } from '../core/errors/errors-data.js';
import { AstroError } from '../core/errors/index.js';
import type { AstroConfig, Locales, ValidRedirectStatus } from '../types/public/config.js';
import type { APIContext } from '../types/public/context.js';
import { createI18nMiddleware } from './middleware.js';
import type { RoutingStrategies } from './utils.js';

export function requestHasLocale(locales: Locales) {
	return function (context: APIContext): boolean {
		return pathHasLocale(context.url.pathname, locales);
	};
}

// Checks if the pathname has any locale
export function pathHasLocale(path: string, locales: Locales): boolean {
	const segments = path.split('/');
	for (const segment of segments) {
		const normalizedSegment = normalizeThePath(segment);
		for (const locale of locales) {
			if (typeof locale === 'string') {
				if (normalizeTheLocale(normalizedSegment) === normalizeTheLocale(locale)) {
					return true;
				}
			} else if (normalizedSegment === locale.path) {
				return true;
			}
		}
	}

	return false;
}

type GetLocaleRelativeUrl = GetLocaleOptions & {
	locale: string;
	base: string;
	locales: Locales;
	trailingSlash: AstroConfig['trailingSlash'];
	format: AstroConfig['build']['format'];
	strategy?: RoutingStrategies;
	defaultLocale: string;
	domains: Record<string, string> | undefined;
	path?: string;
};

Review comment (suggestion):
This function is only used here, so there's no need to have a function. We can add a `.map` after the `split` at line 20 and have the mapping logic there.

Also, since this happens for a specific case e.g. `fr-fr.html`, we could leave a comment that explains the `.html` check

Code after:
import { appendForwardSlash, joinPaths } from '@astrojs/internal-helpers/path';
import type { SSRManifest } from '../core/app/types.js';
import { shouldAppendForwardSlash } from '../core/build/util.js';
import { REROUTE_DIRECTIVE_HEADER } from '../core/constants.js';
import { i18nNoLocaleFoundInPath, MissingLocale } from '../core/errors/errors-data.js';
import { AstroError } from '../core/errors/index.js';
import type { AstroConfig, Locales, ValidRedirectStatus } from '../types/public/config.js';
import type { APIContext } from '../types/public/context.js';
import { createI18nMiddleware } from './middleware.js';
import type { RoutingStrategies } from './utils.js';

export function requestHasLocale(locales: Locales) {
	return function (context: APIContext): boolean {
		return pathHasLocale(context.url.pathname, locales);
	};
}

// Checks if the pathname has any locale
export function pathHasLocale(path: string, locales: Locales): boolean {
	// pages that use a locale param ([locale].astro or [locale]/index.astro)
	// and getStaticPaths make [locale].html the pathname during SSG
	// which will not match a configured locale without removing .html
	// as we do in normalizeThePath
	const segments = path.split('/').map(normalizeThePath);
	for (const segment of segments) {
		for (const locale of locales) {
			if (typeof locale === 'string') {
				if (normalizeTheLocale(segment) === normalizeTheLocale(locale)) {
					return true;
				}
			} else if (segment === locale.path) {
				return true;
			}
		}
	}

	return false;
}

type GetLocaleRelativeUrl = GetLocaleOptions & {
	locale: string;
	base: string;
	locales: Locales;
	trailingSlash: AstroConfig['trailingSlash'];
	format: AstroConfig['build']['format'];
	strategy?: RoutingStrategies;
	defaultLocale: string;