/** * Generates a URL-safe slug from text * @param text - Text to convert to slug * @returns URL-safe slug (lowercase, hyphenated) */ export function slugify(text: string): string { return text .normalize('NFD') // Normalize unicode .replace(/[\u0300-\u036f]/g, '') // Remove diacritics .toLowerCase() .trim() .replace(/[^a-z0-9\s-]/g, '') // Remove non-alphanumeric except spaces and hyphens .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/-+/g, '-') // Collapse multiple hyphens .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens } /** * Generates a unique slug by appending a suffix if needed * @param text - Text to convert to slug * @param existingSlugs - Array of existing slugs to avoid collisions * @returns Unique URL-safe slug */ export function uniqueSlugify(text: string, existingSlugs: string[]): string { const baseSlug = slugify(text) if (!existingSlugs.includes(baseSlug)) { return baseSlug } // Append incrementing numbers until unique let counter = 1 let slug = `${baseSlug}-${counter}` while (existingSlugs.includes(slug)) { counter++ slug = `${baseSlug}-${counter}` } return slug }