fix: i18n gaps in popular searches and compare table
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- SearchFacade.popularSearches hardcoded English titles regardless of active locale. Converted to a getter using translate.t() for the displayed title/text; the underlying search query stays the stable English canonical term the backend index matches against. - Compare table and compare page rendered product.name raw instead of through getTranslatedField(), same pattern used everywhere else product titles are shown (catalog, product detail). - SearchTrendingService.loadTrending() is a genuine backend gap (no trending-search endpoint exists) - already degrades gracefully, documented as a gap in BACKEND-API-REFERENCE.md \u00a712.6 rather than faked client-side. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -522,3 +522,13 @@ Separately, `createCartPayment()` (payment-gateway charge creation) still sends
|
||||
{ "telegramUserId": "8823771" }
|
||||
```
|
||||
`telegramUserId` may be `null` for a non-Telegram web session - decide whether to also accept an email address as an alternative identifier (the frontend has no email capture on this flow today, so that would need a small frontend addition too). Once this ships, the frontend's localStorage fallback becomes purely a resilience path rather than the common case, and could optionally sync any locally-queued subscriptions on next successful call.
|
||||
|
||||
### 12.6 Trending search terms
|
||||
|
||||
**Gap:** `SearchTrendingService.loadTrending()` is a stub returning `of(null)` - no trending-searches endpoint exists. It already degrades gracefully (UI hides the trending section rather than showing an error), so this is purely a missing-feature gap, not a bug.
|
||||
|
||||
**Ask:** an endpoint returning the top N search queries over some recent window, e.g.:
|
||||
```json
|
||||
{ "trending": [{ "query": "wireless earbuds", "count": 214 }, { "query": "winter jacket", "count": 187 }] }
|
||||
```
|
||||
Once it exists, wire `loadTrending()` to it and map `query` -> `SearchSuggestion.title/text`.
|
||||
|
||||
@@ -55,40 +55,47 @@ export class SearchFacade {
|
||||
|
||||
readonly state = this.store.state;
|
||||
|
||||
readonly popularSearches: SearchSuggestion[] = [
|
||||
{
|
||||
id: 'popular-smartphones',
|
||||
type: 'collection',
|
||||
title: 'Smartphones',
|
||||
text: 'Smartphones',
|
||||
icon: 'trendingUp',
|
||||
target: { route: '/search', query: { q: 'Smartphones' } }
|
||||
},
|
||||
{
|
||||
id: 'popular-sneakers',
|
||||
type: 'collection',
|
||||
title: 'Sneakers',
|
||||
text: 'Sneakers',
|
||||
icon: 'trendingUp',
|
||||
target: { route: '/search', query: { q: 'Sneakers' } }
|
||||
},
|
||||
{
|
||||
id: 'popular-headphones',
|
||||
type: 'collection',
|
||||
title: 'Headphones',
|
||||
text: 'Headphones',
|
||||
icon: 'trendingUp',
|
||||
target: { route: '/search', query: { q: 'Headphones' } }
|
||||
},
|
||||
{
|
||||
id: 'popular-laptops',
|
||||
type: 'collection',
|
||||
title: 'Laptops',
|
||||
text: 'Laptops',
|
||||
icon: 'trendingUp',
|
||||
target: { route: '/search', query: { q: 'Laptops' } }
|
||||
},
|
||||
];
|
||||
/**
|
||||
* Search query text stays the stable English canonical term (what the
|
||||
* backend search index matches against); only the displayed title/text
|
||||
* are translated.
|
||||
*/
|
||||
get popularSearches(): SearchSuggestion[] {
|
||||
return [
|
||||
{
|
||||
id: 'popular-smartphones',
|
||||
type: 'collection',
|
||||
title: this.translate.t('search.popularSmartphones'),
|
||||
text: this.translate.t('search.popularSmartphones'),
|
||||
icon: 'trendingUp',
|
||||
target: { route: '/search', query: { q: 'Smartphones' } }
|
||||
},
|
||||
{
|
||||
id: 'popular-sneakers',
|
||||
type: 'collection',
|
||||
title: this.translate.t('search.popularSneakers'),
|
||||
text: this.translate.t('search.popularSneakers'),
|
||||
icon: 'trendingUp',
|
||||
target: { route: '/search', query: { q: 'Sneakers' } }
|
||||
},
|
||||
{
|
||||
id: 'popular-headphones',
|
||||
type: 'collection',
|
||||
title: this.translate.t('search.popularHeadphones'),
|
||||
text: this.translate.t('search.popularHeadphones'),
|
||||
icon: 'trendingUp',
|
||||
target: { route: '/search', query: { q: 'Headphones' } }
|
||||
},
|
||||
{
|
||||
id: 'popular-laptops',
|
||||
type: 'collection',
|
||||
title: this.translate.t('search.popularLaptops'),
|
||||
text: this.translate.t('search.popularLaptops'),
|
||||
icon: 'trendingUp',
|
||||
target: { route: '/search', query: { q: 'Laptops' } }
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
const history = this.historyService.getSnapshot();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<th scope="col">{{ 'ux.compareAttribute' | translate }}</th>
|
||||
@for (product of products; track product.itemID) {
|
||||
<th scope="col">
|
||||
<div class="compare-product-title">{{ product.name }}</div>
|
||||
<div class="compare-product-title">{{ productTitle(product) }}</div>
|
||||
</th>
|
||||
}
|
||||
</tr>
|
||||
|
||||
@@ -2,6 +2,8 @@ import { ChangeDetectionStrategy, Component, Input, computed, inject } from '@an
|
||||
import { Product } from '../../../../../core/products/models/product-domain.model';
|
||||
import { TranslateService } from '../../../../../i18n/translate.service';
|
||||
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
|
||||
import { LanguageService } from '../../../../../services/language.service';
|
||||
import { getTranslatedField } from '../../../../../utils/item.utils';
|
||||
|
||||
interface CompareRow {
|
||||
key: string;
|
||||
@@ -27,6 +29,7 @@ const STOCK_LABEL_KEYS: Record<string, string> = {
|
||||
})
|
||||
export class CompareTableComponent {
|
||||
private readonly i18n = inject(TranslateService);
|
||||
private readonly languageService = inject(LanguageService);
|
||||
|
||||
@Input() products: Product[] = [];
|
||||
@Input() hideIdentical = false;
|
||||
@@ -61,6 +64,10 @@ export class CompareTableComponent {
|
||||
return this.hideIdentical ? baseRows.filter(row => !row.identical) : baseRows;
|
||||
});
|
||||
|
||||
productTitle(product: Product): string {
|
||||
return getTranslatedField(product, 'name', this.languageService.currentLanguage());
|
||||
}
|
||||
|
||||
isDifferentRow(row: CompareRow): boolean {
|
||||
return this.highlightDifferences && !row.identical;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<section class="compare-products-list">
|
||||
@for (product of products(); track product.itemID) {
|
||||
<article class="compare-product-chip">
|
||||
<a [routerLink]="['/product', product.itemID] | langRoute">{{ product.name }}</a>
|
||||
<a [routerLink]="['/product', product.itemID] | langRoute">{{ productTitle(product) }}</a>
|
||||
<button type="button" [attr.aria-label]="'ux.removeFromCompare' | translate" (click)="remove(product.itemID)">×</button>
|
||||
</article>
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import { Product } from '../../../../../core/products/models/product-domain.mode
|
||||
import { UserExperienceFacade } from '../../../../../facades/platform/user-experience.facade';
|
||||
import { TranslatePipe } from '../../../../../i18n/translate.pipe';
|
||||
import { LangRoutePipe } from '../../../../../pipes/lang-route.pipe';
|
||||
import { LanguageService } from '../../../../../services/language.service';
|
||||
import { getTranslatedField } from '../../../../../utils/item.utils';
|
||||
import { DEFAULT_USER_EXPERIENCE_CONFIG } from '../../../../../shared/models/config';
|
||||
import { CompareTableComponent } from '../components/compare-table.component';
|
||||
import { EmptyStateComponent } from '../../../../../shared/ui/empty-state/empty-state.component';
|
||||
@@ -21,6 +23,7 @@ import { ButtonComponent } from '../../../../../shared/ui/button/button.componen
|
||||
export class ComparePageComponent {
|
||||
private readonly uxFacade = inject(UserExperienceFacade);
|
||||
private readonly configService = inject(ConfigService);
|
||||
private readonly languageService = inject(LanguageService);
|
||||
|
||||
private readonly compareConfig = this.resolveCompareConfig();
|
||||
|
||||
@@ -39,6 +42,10 @@ export class ComparePageComponent {
|
||||
this.uxFacade.clearCompare();
|
||||
}
|
||||
|
||||
productTitle(product: Product): string {
|
||||
return getTranslatedField(product, 'name', this.languageService.currentLanguage());
|
||||
}
|
||||
|
||||
private resolveCompareConfig() {
|
||||
const raw = (this.configService.getBootstrapSnapshot() as any)?.userExperience?.compare ?? {};
|
||||
return {
|
||||
|
||||
@@ -144,6 +144,10 @@ export const en: Translations = {
|
||||
noResultsHint: 'Try changing your query or using different keywords',
|
||||
emptyResultsAria: 'Empty search results',
|
||||
popularCategories: 'Popular categories',
|
||||
popularSmartphones: 'Smartphones',
|
||||
popularSneakers: 'Sneakers',
|
||||
popularHeadphones: 'Headphones',
|
||||
popularLaptops: 'Laptops',
|
||||
recommendedProducts: 'Recommended products',
|
||||
aiSuggestionHint: 'AI suggestion (future)',
|
||||
suggestionType: {
|
||||
|
||||
@@ -144,6 +144,10 @@ export const hy: Translations = {
|
||||
noResultsHint: 'Փորձեք փոխել հարցումը կամ օգտագործել այլ բանալի բառեր',
|
||||
emptyResultsAria: 'Դատարկ որոնման արդյունքներ',
|
||||
popularCategories: 'Հանրաճանաչ կատեգորիաներ',
|
||||
popularSmartphones: 'Սմարթֆոններ',
|
||||
popularSneakers: 'Կեդեր',
|
||||
popularHeadphones: 'Ականջակալներ',
|
||||
popularLaptops: 'Նոութբուքեր',
|
||||
recommendedProducts: 'Առաջարկվող ապրանքներ',
|
||||
aiSuggestionHint: 'AI առաջարկ (ապագայում)',
|
||||
suggestionType: {
|
||||
|
||||
@@ -144,6 +144,10 @@ export const ru: Translations = {
|
||||
noResultsHint: 'Попробуйте изменить запрос или используйте другие ключевые слова',
|
||||
emptyResultsAria: 'Пустые результаты поиска',
|
||||
popularCategories: 'Популярные категории',
|
||||
popularSmartphones: 'Смартфоны',
|
||||
popularSneakers: 'Кроссовки',
|
||||
popularHeadphones: 'Наушники',
|
||||
popularLaptops: 'Ноутбуки',
|
||||
recommendedProducts: 'Рекомендуемые товары',
|
||||
aiSuggestionHint: 'AI-подсказка (в будущем)',
|
||||
suggestionType: {
|
||||
|
||||
@@ -142,6 +142,10 @@ export interface Translations {
|
||||
noResultsHint: string;
|
||||
emptyResultsAria: string;
|
||||
popularCategories: string;
|
||||
popularSmartphones: string;
|
||||
popularSneakers: string;
|
||||
popularHeadphones: string;
|
||||
popularLaptops: string;
|
||||
recommendedProducts: string;
|
||||
aiSuggestionHint: string;
|
||||
suggestionType: {
|
||||
|
||||
Reference in New Issue
Block a user