SEO/AEO, Farb schema, breadcrumbs

This commit is contained in:
2025-11-29 23:41:54 +01:00
parent 4fa24c8f3d
commit d2953fd0d9
87 changed files with 5672 additions and 579 deletions

View File

@@ -0,0 +1,120 @@
import { Injectable, inject } from '@angular/core';
import { SelectOptionsService } from './select-options.service';
/**
* Service for generating SEO-optimized alt text for images
* Ensures consistent, keyword-rich alt text across the application
*/
@Injectable({
providedIn: 'root'
})
export class AltTextService {
private selectOptions = inject(SelectOptionsService);
/**
* Generate alt text for business listing images
* Format: "Business Name - Business Type for sale in City, State"
* Example: "Italian Restaurant - Restaurant for sale in Austin, TX"
*/
generateBusinessListingAlt(listing: any): string {
const location = this.getLocationString(listing.location);
const businessType = listing.type ? this.selectOptions.getBusiness(listing.type) : 'Business';
return `${listing.title} - ${businessType} for sale in ${location}`;
}
/**
* Generate alt text for commercial property listing images
* Format: "Property Type for sale - Title in City, State"
* Example: "Retail Space for sale - Downtown storefront in Miami, FL"
*/
generatePropertyListingAlt(listing: any): string {
const location = this.getLocationString(listing.location);
const propertyType = listing.type ? this.selectOptions.getCommercialProperty(listing.type) : 'Commercial Property';
return `${propertyType} for sale - ${listing.title} in ${location}`;
}
/**
* Generate alt text for broker/user profile photos
* Format: "First Last - Customer Type broker profile photo"
* Example: "John Smith - Business broker profile photo"
*/
generateBrokerProfileAlt(user: any): string {
const name = `${user.firstname || ''} ${user.lastname || ''}`.trim() || 'Broker';
const customerType = user.customerSubType ? this.selectOptions.getCustomerSubType(user.customerSubType) : 'Business';
return `${name} - ${customerType} broker profile photo`;
}
/**
* Generate alt text for company logos
* Format: "Company Name company logo" or "Owner Name company logo"
* Example: "ABC Realty company logo"
*/
generateCompanyLogoAlt(companyName?: string, ownerName?: string): string {
const name = companyName || ownerName || 'Company';
return `${name} company logo`;
}
/**
* Generate alt text for listing card logo images
* Includes business name and location for context
*/
generateListingCardLogoAlt(listing: any): string {
const location = this.getLocationString(listing.location);
return `${listing.title} - Business for sale in ${location}`;
}
/**
* Generate alt text for property images in detail view
* Format: "Title - Property Type image (index)"
* Example: "Downtown Office Space - Office building image 1 of 5"
*/
generatePropertyImageAlt(title: string, propertyType: string, imageIndex?: number, totalImages?: number): string {
let alt = `${title} - ${propertyType}`;
if (imageIndex !== undefined && totalImages !== undefined && totalImages > 1) {
alt += ` image ${imageIndex + 1} of ${totalImages}`;
} else {
alt += ' image';
}
return alt;
}
/**
* Generate alt text for business images in detail view
* Format: "Title - Business Type image (index)"
*/
generateBusinessImageAlt(title: string, businessType: string, imageIndex?: number, totalImages?: number): string {
let alt = `${title} - ${businessType}`;
if (imageIndex !== undefined && totalImages !== undefined && totalImages > 1) {
alt += ` image ${imageIndex + 1} of ${totalImages}`;
} else {
alt += ' image';
}
return alt;
}
/**
* Helper: Get location string from location object
* Returns: "City, STATE" or "County, STATE" or "STATE"
*/
private getLocationString(location: any): string {
if (!location) return 'United States';
const city = location.name || location.county;
const state = location.state || '';
if (city && state) {
return `${city}, ${state}`;
} else if (state) {
return this.selectOptions.getState(state) || state;
}
return 'United States';
}
}