fix(widgets): hero title/subtitle/CTA now locale-aware
P0-3: hero widget text rendered hardcoded English on every locale (ru/hy included) because HeroWidgetData source props were plain strings with no per-locale variant, unlike nav labels which already support a locale map (NavigationLocalizedText). DataSourceResolverService.toHeroData() now accepts either a plain string (existing tenants unaffected) or a per-locale text map for title/subtitle/ctaLabel and each slide's fields, resolved against the active language the same way footer group titles already are (LocalizedTextContent, current lang -> en -> first available). Updated the mock bootstrap fixture's hero props to a real ru/en/hy map so the demo tenant shows translated copy instead of English on every locale. No editor UI change needed: the Widgets section's existing JSON-fallback editor already accepts arbitrary prop shapes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,8 @@ import { Injectable, inject } from '@angular/core';
|
||||
import { Observable, of, map, switchMap } from 'rxjs';
|
||||
import { CategoryFacade } from '../../facades/platform/category.facade';
|
||||
import { ProductFacade } from '../../facades/platform/product.facade';
|
||||
import { SectionConfig, WidgetConfig } from '../../shared/models/config';
|
||||
import { LanguageService } from '../../services/language.service';
|
||||
import { LocalizedTextContent, SectionConfig, WidgetConfig } from '../../shared/models/config';
|
||||
import { WidgetManifestEntry, WidgetDataSourceName } from '../contracts/widget-manifest.contract';
|
||||
import { BannerWidgetData, CategoriesWidgetData, FooterWidgetData, HeroWidgetData, HtmlWidgetData, PartnersWidgetData, ProductCollectionWidgetData } from '../contracts/widget-data.contract';
|
||||
import { WidgetManifestService } from '../registry/widget-manifest.service';
|
||||
@@ -14,6 +15,7 @@ export class DataSourceResolverService {
|
||||
private readonly widgetManifest = inject(WidgetManifestService);
|
||||
private readonly categoryFacade = inject(CategoryFacade);
|
||||
private readonly productFacade = inject(ProductFacade);
|
||||
private readonly languageService = inject(LanguageService);
|
||||
|
||||
resolve(widget: WidgetConfig, section: SectionConfig): Observable<unknown> {
|
||||
return this.widgetManifest.getWidget(widget.type).pipe(
|
||||
@@ -84,15 +86,32 @@ export class DataSourceResolverService {
|
||||
return typeof source === 'string' ? source as WidgetDataSourceName : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hero text props accept either a plain string (existing tenants, unchanged) or a
|
||||
* per-locale map (`{ ru: '...', en: '...' }`, same shape as NavigationLocalizedText)
|
||||
* so the hero doesn't render in one hardcoded language on every locale.
|
||||
*/
|
||||
private resolveLocalizedWidgetText(value: unknown): string | undefined {
|
||||
if (value == null) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value !== 'object') {
|
||||
return String(value);
|
||||
}
|
||||
const lang = this.languageService.currentLanguage();
|
||||
const text = value as LocalizedTextContent;
|
||||
return text[lang] ?? text['en'] ?? Object.values(text)[0];
|
||||
}
|
||||
|
||||
private toHeroData(section: SectionConfig, settings: Record<string, unknown>): HeroWidgetData {
|
||||
const rawSlides = settings['slides'];
|
||||
const slides = Array.isArray(rawSlides)
|
||||
? rawSlides
|
||||
.filter((slide): slide is Record<string, unknown> => !!slide && typeof slide === 'object')
|
||||
.map(slide => ({
|
||||
title: String(slide['title'] ?? ''),
|
||||
subtitle: slide['subtitle'] != null ? String(slide['subtitle']) : undefined,
|
||||
ctaLabel: slide['ctaLabel'] != null ? String(slide['ctaLabel']) : undefined,
|
||||
title: this.resolveLocalizedWidgetText(slide['title']) ?? '',
|
||||
subtitle: this.resolveLocalizedWidgetText(slide['subtitle']),
|
||||
ctaLabel: this.resolveLocalizedWidgetText(slide['ctaLabel']),
|
||||
}))
|
||||
.filter(slide => slide.title)
|
||||
: undefined;
|
||||
@@ -100,9 +119,9 @@ export class DataSourceResolverService {
|
||||
return {
|
||||
section,
|
||||
settings,
|
||||
title: String(settings['title'] ?? ''),
|
||||
subtitle: settings['subtitle'] != null ? String(settings['subtitle']) : undefined,
|
||||
ctaLabel: settings['ctaLabel'] != null ? String(settings['ctaLabel']) : undefined,
|
||||
title: this.resolveLocalizedWidgetText(settings['title']) ?? '',
|
||||
subtitle: this.resolveLocalizedWidgetText(settings['subtitle']),
|
||||
ctaLabel: this.resolveLocalizedWidgetText(settings['ctaLabel']),
|
||||
slides,
|
||||
autoplay: settings['autoplay'] === true,
|
||||
};
|
||||
|
||||
@@ -398,9 +398,21 @@
|
||||
},
|
||||
"visible": true,
|
||||
"props": {
|
||||
"title": "Welcome to Marketplace Platform",
|
||||
"subtitle": "Configuration-driven multi-tenant commerce",
|
||||
"ctaLabel": "Start Shopping"
|
||||
"title": {
|
||||
"ru": "Добро пожаловать на Маркетплейс",
|
||||
"en": "Welcome to Marketplace Platform",
|
||||
"hy": "Բարի գալուստ Մարքեթփլեյս"
|
||||
},
|
||||
"subtitle": {
|
||||
"ru": "Конфигурируемая мультитенантная торговая площадка",
|
||||
"en": "Configuration-driven multi-tenant commerce",
|
||||
"hy": "Կոնֆիգուրացիայով կառավարվող բազմատենանտ առևտրի հարթակ"
|
||||
},
|
||||
"ctaLabel": {
|
||||
"ru": "Начать покупки",
|
||||
"en": "Start Shopping",
|
||||
"hy": "Սկսել գնումները"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -33,9 +33,21 @@
|
||||
"version": "1.0.0",
|
||||
"visible": true,
|
||||
"props": {
|
||||
"title": "Welcome to Marketplace Platform",
|
||||
"subtitle": "Configuration-driven multi-tenant commerce",
|
||||
"ctaLabel": "Start Shopping"
|
||||
"title": {
|
||||
"ru": "Добро пожаловать на Маркетплейс",
|
||||
"en": "Welcome to Marketplace Platform",
|
||||
"hy": "Բարի գալուստ Մարքեթփլեյս"
|
||||
},
|
||||
"subtitle": {
|
||||
"ru": "Конфигурируемая мультитенантная торговая площадка",
|
||||
"en": "Configuration-driven multi-tenant commerce",
|
||||
"hy": "Կոնֆիգուրացիայով կառավարվող բազմատենանտ առևտրի հարթակ"
|
||||
},
|
||||
"ctaLabel": {
|
||||
"ru": "Начать покупки",
|
||||
"en": "Start Shopping",
|
||||
"hy": "Սկսել գնումները"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user