/** * PathnameInterpreter * * Server-only utility for interpreting URL pathnames and extracting locale information. * Strips locale prefix if present and returns the logical pathname. * * Examples: * - '/de/dashboard' → { locale: 'de', logicalPathname: '/dashboard' } * - '/dashboard' → { locale: null, logicalPathname: '/dashboard' } * - '/' → { locale: null, logicalPathname: '/' } * - '/999/dashboard' → { locale: null, logicalPathname: '/999/dashboard' } */ export interface PathnameInterpretation { locale: string | null; logicalPathname: string; } export class PathnameInterpreter { /** * Interprets a pathname and extracts locale information * * @param pathname - The URL pathname to interpret * @returns Object with locale (if valid 2-letter code) and logical pathname */ interpret(pathname: string): PathnameInterpretation { // Handle empty path if (pathname === '') { return { locale: null, logicalPathname: '', }; } // Handle root path if (pathname === '/') { return { locale: null, logicalPathname: '/', }; } // Normalize pathname (remove trailing slash for consistent processing) const normalizedPathname = pathname.endsWith('/') && pathname.length > 1 ? pathname.slice(0, -1) : pathname; // Split into segments const segments = normalizedPathname.split('/').filter(Boolean); // No segments to process if (segments.length === 0) { return { locale: null, logicalPathname: '/', }; } // Check if first segment is a valid 2-letter locale code const firstSegment = segments[0]; if (this.isValidLocale(firstSegment)) { // Valid locale detected - strip it const remainingSegments = segments.slice(1); const logicalPathname = remainingSegments.length > 0 ? '/' + remainingSegments.join('/') : '/'; return { locale: firstSegment, logicalPathname, }; } // No valid locale prefix found return { locale: null, logicalPathname: pathname, }; } /** * Validates if a string is a valid 2-letter locale code * Must be exactly 2 lowercase letters (a-z) * * @param segment - The segment to validate * @returns True if valid locale code */ private isValidLocale(segment: string): boolean { // Must be exactly 2 characters if (segment.length !== 2) { return false; } // Must be lowercase letters only (a-z) return /^[a-z]{2}$/.test(segment); } }