97 lines
2.5 KiB
TypeScript
97 lines
2.5 KiB
TypeScript
/**
|
|
* 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);
|
|
}
|
|
} |