import { addFilePrefix } from './common' import { getCurrentLanguage } from './i18n' const BANNER_TEXT_FALLBACK_MAP = { title: { '绿居·和山国际公寓': 'Luju Heshan Intl. Apartments', '和山国际公寓': 'Heshan Intl. Apartments', '乾晖里': 'Qianhui Residence' }, subtitle: { '国际轻奢公寓': 'International Light Luxury Apartment', '城芯静境': 'Urban Quiet Retreat' }, tab: { '国际轻奢公寓': 'Light Luxury Living', '城芯静境': 'Urban Quiet Retreat' }, desc: { '山境雅境 | 涉外住区 | 精致旅居': 'Mountain Views | Global Living | Boutique Stay' }, areaLabel: {} } const VALUE_TEXT_FALLBACK_MAP = { tags: { '押一付一': 'Monthly Deposit', '大型超市': 'Large Supermarket', '地铁口': 'Near Metro', '生活便利': 'Convenient Living', '独立厨卫': 'Private Kitchen & Bath', '商圈环绕': 'City Center', '民水民电': 'Residential Utilities', '拎包入住': 'Move-in Ready', '近地铁': 'Near Metro', '智能家居': 'Smart Home' }, payMethod: { '押一付一': 'Monthly Deposit', '押一付三': 'Deposit 1, Pay 3', '押一付六': 'Deposit 1, Pay 6', '押一付年': 'Deposit 1, Annual Pay', '月付': 'Monthly Pay', '季付': 'Quarterly Pay', '半年付': 'Semiannual Pay', '年付': 'Annual Pay' }, facility: { '大型超市': 'Large Supermarket', '地铁口': 'Near Metro', '快递柜': 'Parcel Locker', '健身房': 'Gym', '自习室': 'Study Room', '洗衣房': 'Laundry Room', '停车场': 'Parking', '便利店': 'Convenience Store', '公共厨房': 'Shared Kitchen', '休闲区': 'Lounge' } } function firstNonEmptyText(...values) { const matched = values.find((value) => String(value || '').trim()) return matched ? String(matched).trim() : '' } function normalizePayCount(value) { if (value === '' || value === null || typeof value === 'undefined') { return null } const parsed = Number(value) if (Number.isFinite(parsed) && parsed >= 0) { return parsed } return null } function formatPayCount(value) { return String(value) } function buildPayMethodFromPricePlan(pricePlanDetail = {}, fallbackText = '') { const deposit = normalizePayCount(pricePlanDetail.deposit) const prepay = normalizePayCount(pricePlanDetail.prepay) if (deposit !== null && prepay !== null) { return `押${formatPayCount(deposit)}付${formatPayCount(prepay)}` } return String(fallbackText || '').trim() } export function compactEnglishText(text = '', maxLength = 24) { const rawText = String(text || '').trim() if (!rawText) { return '' } const compacted = rawText .replace(/\bInternational\b/gi, 'Intl.') .replace(/\bApartment(s)?\b/gi, 'Apt$1') .replace(/\bResidence\b/gi, 'Resid.') .replace(/\bBuilding\b/gi, 'Bldg.') .replace(/\bCommunity\b/gi, 'Comm.') .replace(/\bBoulevard\b/gi, 'Blvd.') .replace(/\bRoad\b/gi, 'Rd.') .replace(/\bStreet\b/gi, 'St.') .replace(/\bAvenue\b/gi, 'Ave.') .replace(/\s{2,}/g, ' ') .trim() if (compacted.length <= maxLength) { return compacted } return `${compacted.slice(0, Math.max(maxLength - 3, 1)).trim()}...` } function getSelectRoomText(key = '', params = {}) { const isEn = getCurrentLanguage() === 'en' const map = { featuredContent: isEn ? 'Featured Picks' : '精选内容', recommendedTab: isEn ? `Featured ${params.index}` : `推荐${params.index}` } return map[key] || '' } function localizeKnownValue(field, text = '', language = getCurrentLanguage()) { const normalizedText = String(text || '').trim() if (!normalizedText || language !== 'en') { return normalizedText } if (field === 'payMethod') { const payMethodMatch = normalizedText.match(/^押(\d+(?:\.\d+)?)付(\d+(?:\.\d+)?)$/) if (payMethodMatch) { return `Deposit ${payMethodMatch[1]}, Pay ${payMethodMatch[2]}` } } const fieldMap = VALUE_TEXT_FALLBACK_MAP[field] || {} return fieldMap[normalizedText] || normalizedText } export function localizeHousingTag(tag = '', language = getCurrentLanguage()) { return localizeKnownValue('tags', tag, language) } export function localizePayMethodText(payMethod = '', language = getCurrentLanguage()) { return localizeKnownValue('payMethod', payMethod, language) } export function localizeFacilityName(name = '', language = getCurrentLanguage()) { return localizeKnownValue('facility', name, language) } function resolveBannerFallbackText(field, text) { const normalizedText = String(text || '').trim() if (!normalizedText) { return '' } return BANNER_TEXT_FALLBACK_MAP[field] && BANNER_TEXT_FALLBACK_MAP[field][normalizedText] ? BANNER_TEXT_FALLBACK_MAP[field][normalizedText] : '' } function extractLabelValues(...sources) { const values = sources.flatMap((source) => { if (!Array.isArray(source)) { return [] } return source.map((item) => { if (!item) { return '' } if (typeof item === 'string') { return item } if (typeof item === 'object') { return item.value || item.label || item.name || item.key || '' } return '' }) }) return [...new Set(values.filter(Boolean))] } export function parseBannerExtra(extra) { if (!extra) { return {} } if (typeof extra === 'object') { return extra } try { return JSON.parse(extra) } catch (error) { return {} } } export function normalizeBannerImage(imageUrl) { if (!imageUrl) { return '' } if (/^https?:\/\//i.test(imageUrl)) { return imageUrl } return addFilePrefix(imageUrl) } function isImageLikeLogo(value) { if (!value) { return false } return /^(https?:)?\/\//i.test(value) || value.startsWith('/') || value.startsWith('data:image') } function normalizeBannerLogo(extra = {}) { const rawType = String(extra.logoType || '').trim().toLowerCase() const rawLogo = String(extra.logo || '').trim() const rawLogoIcon = String(extra.logoIcon || '').trim() const rawLogoImage = String(extra.logoImage || '').trim() const isImage = rawType === 'image' || (!rawType && !rawLogoImage && isImageLikeLogo(rawLogo)) const logoImageSource = rawLogoImage || (isImage ? rawLogo : '') const logoIconSource = rawLogoIcon || (!isImage ? rawLogo : '') return { logoType: isImage ? 'image' : 'icon', logoImage: normalizeBannerImage(logoImageSource), logoIcon: logoIconSource, } } function normalizeBannerLogoIconClass(icon) { const normalizedIcon = String(icon || '').trim().toLowerCase() if (!normalizedIcon) { return '' } return normalizedIcon.startsWith('ri-') ? normalizedIcon : `ri-${normalizedIcon}` } export function normalizeBannerItem(item = {}, index = 0) { const extra = parseBannerExtra(item.extra) const title = firstNonEmptyText(item.title, item.name, extra.title, extra.mainTitle, extra.titleZh, getSelectRoomText('featuredContent')) const titleEn = firstNonEmptyText( item.titleEn, item.title_en, item.nameEn, item.name_en, extra.titleEn, extra.title_en, extra.enTitle, extra.mainTitleEn, extra.titleEnUs, resolveBannerFallbackText('title', title), 'Featured Picks' ) const subtitle = firstNonEmptyText(extra.subTitle, extra.subtitle, extra.remark, extra.note, item.remark) const subtitleEn = firstNonEmptyText( extra.subTitleEn, extra.subtitleEn, extra.sub_title_en, extra.enSubtitle, extra.remarkEn, extra.remark_en, extra.noteEn, item.remarkEn, item.remark_en, resolveBannerFallbackText('subtitle', subtitle) ) const desc = firstNonEmptyText(extra.desc, extra.description, extra.mainDesc, extra.slogan) const descEn = firstNonEmptyText( extra.descEn, extra.descriptionEn, extra.desc_en, extra.description_en, extra.enDesc, extra.mainDescEn, extra.sloganEn, resolveBannerFallbackText('desc', desc) ) const areaLabel = firstNonEmptyText(extra.areaName, extra.areaLabel, extra.area) const areaLabelEn = firstNonEmptyText(extra.areaNameEn, extra.areaLabelEn, extra.area_name_en, extra.area_label_en, extra.enAreaName, resolveBannerFallbackText('areaLabel', areaLabel)) const tab = firstNonEmptyText(extra.tab, extra.topTab, title, getSelectRoomText('recommendedTab', { index: index + 1 })) const tabEn = firstNonEmptyText(extra.tabEn, extra.tab_en, extra.enTab, extra.topTabEn, resolveBannerFallbackText('tab', tab), titleEn, `Featured ${index + 1}`) const roomTypes = Array.isArray(extra.roomTypes) ? extra.roomTypes : [] const tags = Array.isArray(extra.tags) && extra.tags.length ? extra.tags.filter(Boolean) : [] const logoInfo = normalizeBannerLogo(extra) return { id: item.no || item.code || item.id || `banner-${index}`, tab, tabEn, tabEnDisplay: compactEnglishText(tabEn, 18), title, titleEn, titleEnDisplay: compactEnglishText(titleEn, 24), subtitle, subtitleEn, subtitleEnDisplay: compactEnglishText(subtitleEn, 28), desc, descEn, descEnDisplay: compactEnglishText(descEn, 36), areaCode: extra.areaCode || extra.area || '', areaLabel, areaLabelEn, areaLabelEnDisplay: compactEnglishText(areaLabelEn, 22), roomTypes, recommendLabel: extra.recommendLabel || extra.recommend || '', image: normalizeBannerImage(item.imageUrl || extra.imageUrl), tags, logoType: logoInfo.logoType, logoImage: logoInfo.logoImage, logoIcon: logoInfo.logoIcon, logoIconClass: normalizeBannerLogoIconClass(logoInfo.logoIcon), } } export function normalizeImageList(item = {}) { const candidates = [] const listFields = [item.images, item.imageUrls, item.pictures, item.pictureList] listFields.forEach((field) => { if (Array.isArray(field)) { field.forEach((image) => { const normalized = normalizeBannerImage(image) if (normalized) { candidates.push(normalized) } }) } }) ;[item.imageUrl, item.image, item.cover, item.picture].forEach((image) => { const normalized = normalizeBannerImage(image) if (normalized) { candidates.push(normalized) } }) return [...new Set(candidates)] } export function normalizeAreaRoomType(item = {}) { const price = item.pricePlanDetail && typeof item.pricePlanDetail.price !== 'undefined' ? String(item.pricePlanDetail.price) : '' const priceValue = item.pricePlanDetail && typeof item.pricePlanDetail.price !== 'undefined' ? Number(item.pricePlanDetail.price) : Number.MAX_SAFE_INTEGER const tags = extractLabelValues(item.labelList, item.tags, item.labels, item.featureTags) return { code: item.code || '', areaCode: item.areaCode || '', buildingCode: item.buildingCode || item.buildingNo || '', buildingName: item.buildingName || '', buildingSectionCode: item.buildingSectionCode || item.unitCode || '', buildingSectionName: item.buildingSectionName || item.unitName || '', name: item.name || '', nameEn: item.nameEn || '', nameEnDisplay: compactEnglishText(item.nameEn || '', 22), description: item.description || '', descriptionEn: item.descriptionEn || '', descriptionEnDisplay: compactEnglishText(item.descriptionEn || '', 34), images: normalizeImageList(item), price, priceValue, payMethod: buildPayMethodFromPricePlan(item.pricePlanDetail, item.pricePlanDetail && item.pricePlanDetail.payMethod), tags } } export function normalizeAreaItem(item = {}) { const roomTypes = Array.isArray(item.roomTypes) ? item.roomTypes .map((roomType) => normalizeAreaRoomType(roomType)) .filter((roomType) => roomType.code || roomType.name) : [] const tags = extractLabelValues(item.labelList, item.tags, item.labels, item.featureTags) return { code: item.code || '', name: item.name || '', nameEn: item.nameEn || '', nameEnDisplay: compactEnglishText(item.nameEn || '', 24), description: item.description || '', descriptionEn: item.descriptionEn || '', descriptionEnDisplay: compactEnglishText(item.descriptionEn || '', 36), address: item.address || '', addressEn: item.addressEn || '', addressEnDisplay: compactEnglishText(item.addressEn || '', 32), latitude: item.latitude || item.lat || '', longitude: item.longitude || item.lng || '', images: normalizeImageList(item), tags, roomTypes } }