diff --git a/frontend/src/app/core/constants/colors.const.ts b/frontend/src/app/core/constants/colors.const.ts index 9a744c4..1223848 100644 --- a/frontend/src/app/core/constants/colors.const.ts +++ b/frontend/src/app/core/constants/colors.const.ts @@ -11,6 +11,8 @@ export interface ColorCategory { colors: ColorOption[]; } +const DEFAULT_BRAND_COLOR = '#facf0a'; + export const PRODUCT_COLORS: ColorCategory[] = [ { name: 'COLOR.CATEGORY_GLOSSY', @@ -38,10 +40,145 @@ export const PRODUCT_COLORS: ColorCategory[] = [ }, ]; -export function getColorHex(value: string): string { - for (const cat of PRODUCT_COLORS) { - const found = cat.colors.find((c) => c.value === value); - if (found) return found.hex; - } - return '#facf0a'; // Default Brand Color if not found +const COLOR_HEX_BY_TRANSLATION_KEY: Record = { + ...Object.fromEntries( + PRODUCT_COLORS.flatMap((category) => + category.colors.map((color) => [color.label, color.hex] as const), + ), + ), + 'COLOR.NAME.ORANGE': '#f5a623', + 'COLOR.NAME.GRAY': '#b7b7b7', + 'COLOR.NAME.LIGHT_GRAY': '#d8dadd', + 'COLOR.NAME.DARK_GRAY': '#4f4f4f', + 'COLOR.NAME.PURPLE': '#7b1fa2', + 'COLOR.NAME.BEIGE': '#d4c09a', + 'COLOR.NAME.SAND_BEIGE': '#d7c2a0', +}; + +const COLOR_TRANSLATION_KEY_BY_VALUE: Record = { + black: 'COLOR.NAME.BLACK', + nero: 'COLOR.NAME.BLACK', + noir: 'COLOR.NAME.BLACK', + schwarz: 'COLOR.NAME.BLACK', + white: 'COLOR.NAME.WHITE', + bianco: 'COLOR.NAME.WHITE', + blanc: 'COLOR.NAME.WHITE', + weiss: 'COLOR.NAME.WHITE', + red: 'COLOR.NAME.RED', + rosso: 'COLOR.NAME.RED', + rouge: 'COLOR.NAME.RED', + rot: 'COLOR.NAME.RED', + blue: 'COLOR.NAME.BLUE', + blu: 'COLOR.NAME.BLUE', + bleu: 'COLOR.NAME.BLUE', + blau: 'COLOR.NAME.BLUE', + green: 'COLOR.NAME.GREEN', + verde: 'COLOR.NAME.GREEN', + vert: 'COLOR.NAME.GREEN', + grun: 'COLOR.NAME.GREEN', + yellow: 'COLOR.NAME.YELLOW', + giallo: 'COLOR.NAME.YELLOW', + jaune: 'COLOR.NAME.YELLOW', + gelb: 'COLOR.NAME.YELLOW', + orange: 'COLOR.NAME.ORANGE', + arancione: 'COLOR.NAME.ORANGE', + naranja: 'COLOR.NAME.ORANGE', + gris: 'COLOR.NAME.GRAY', + gray: 'COLOR.NAME.GRAY', + grey: 'COLOR.NAME.GRAY', + grigio: 'COLOR.NAME.GRAY', + grau: 'COLOR.NAME.GRAY', + 'light gray': 'COLOR.NAME.LIGHT_GRAY', + 'light grey': 'COLOR.NAME.LIGHT_GRAY', + 'grigio chiaro': 'COLOR.NAME.LIGHT_GRAY', + 'gris clair': 'COLOR.NAME.LIGHT_GRAY', + hellgrau: 'COLOR.NAME.LIGHT_GRAY', + 'dark gray': 'COLOR.NAME.DARK_GRAY', + 'dark grey': 'COLOR.NAME.DARK_GRAY', + 'grigio scuro': 'COLOR.NAME.DARK_GRAY', + 'gris fonce': 'COLOR.NAME.DARK_GRAY', + dunkelgrau: 'COLOR.NAME.DARK_GRAY', + purple: 'COLOR.NAME.PURPLE', + violet: 'COLOR.NAME.PURPLE', + viola: 'COLOR.NAME.PURPLE', + lila: 'COLOR.NAME.PURPLE', + beige: 'COLOR.NAME.BEIGE', + 'sand beige': 'COLOR.NAME.SAND_BEIGE', + 'beige sabbia': 'COLOR.NAME.SAND_BEIGE', + 'beige sable': 'COLOR.NAME.SAND_BEIGE', + sandbeige: 'COLOR.NAME.SAND_BEIGE', + 'matte black': 'COLOR.NAME.MATTE_BLACK', + 'black matte': 'COLOR.NAME.MATTE_BLACK', + 'nero opaco': 'COLOR.NAME.MATTE_BLACK', + 'noir mat': 'COLOR.NAME.MATTE_BLACK', + 'matt schwarz': 'COLOR.NAME.MATTE_BLACK', + 'schwarz matt': 'COLOR.NAME.MATTE_BLACK', + 'matte white': 'COLOR.NAME.MATTE_WHITE', + 'white matte': 'COLOR.NAME.MATTE_WHITE', + 'bianco opaco': 'COLOR.NAME.MATTE_WHITE', + 'blanc mat': 'COLOR.NAME.MATTE_WHITE', + 'matt weiss': 'COLOR.NAME.MATTE_WHITE', + 'weiss matt': 'COLOR.NAME.MATTE_WHITE', + 'matte gray': 'COLOR.NAME.MATTE_GRAY', + 'matte grey': 'COLOR.NAME.MATTE_GRAY', + 'grigio opaco': 'COLOR.NAME.MATTE_GRAY', + 'gris mat': 'COLOR.NAME.MATTE_GRAY', + 'matt grau': 'COLOR.NAME.MATTE_GRAY', + 'grau matt': 'COLOR.NAME.MATTE_GRAY', +}; + +export function normalizeColorValue(value: string | null | undefined): string { + return String(value ?? '') + .trim() + .toLowerCase() + .replace(/ß/g, 'ss') + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/[_-]+/g, ' ') + .replace(/\s+/g, ' '); +} + +export function getColorTranslationKey( + value: string | null | undefined, +): string | null { + const normalized = normalizeColorValue(value); + return normalized ? COLOR_TRANSLATION_KEY_BY_VALUE[normalized] ?? null : null; +} + +export function getColorLabelToken( + value: string | null | undefined, +): string | null { + const raw = String(value ?? '').trim(); + if (!raw) { + return null; + } + + return getColorTranslationKey(raw) ?? raw; +} + +export function findColorHex(value: string | null | undefined): string | null { + const translationKey = getColorTranslationKey(value); + if (translationKey) { + return COLOR_HEX_BY_TRANSLATION_KEY[translationKey] ?? null; + } + + const normalized = normalizeColorValue(value); + if (!normalized) { + return null; + } + + for (const category of PRODUCT_COLORS) { + const match = category.colors.find( + (color) => normalizeColorValue(color.value) === normalized, + ); + if (match) { + return match.hex; + } + } + + return null; +} + +export function getColorHex(value: string): string { + return findColorHex(value) ?? DEFAULT_BRAND_COLOR; } diff --git a/frontend/src/app/core/layout/navbar.component.html b/frontend/src/app/core/layout/navbar.component.html index 46d2bd8..f402152 100644 --- a/frontend/src/app/core/layout/navbar.component.html +++ b/frontend/src/app/core/layout/navbar.component.html @@ -130,7 +130,7 @@
{{ cartItemName(item) }} @if (cartItemVariant(item); as variant) { - {{ variant }} + {{ variant | translate }} } @if (cartItemColor(item); as color) { @@ -138,7 +138,7 @@ class="color-dot" [style.background-color]="cartItemColorHex(item)" > - {{ color }} + {{ color | translate }} }
diff --git a/frontend/src/app/core/layout/navbar.component.ts b/frontend/src/app/core/layout/navbar.component.ts index 5a7c91a..1e2f870 100644 --- a/frontend/src/app/core/layout/navbar.component.ts +++ b/frontend/src/app/core/layout/navbar.component.ts @@ -15,6 +15,10 @@ import { ShopService, } from '../../features/shop/services/shop.service'; import { finalize } from 'rxjs'; +import { + findColorHex, + getColorLabelToken, +} from '../constants/colors.const'; @Component({ selector: 'app-navbar', @@ -143,15 +147,25 @@ export class NavbarComponent { } cartItemVariant(item: ShopCartItem): string | null { - return item.shopVariantLabel || item.shopVariantColorName || null; + return ( + item.shopVariantLabel || getColorLabelToken(item.shopVariantColorName) + ); } cartItemColor(item: ShopCartItem): string | null { - return item.shopVariantColorName || item.colorCode || null; + return ( + getColorLabelToken(item.shopVariantColorName) ?? + getColorLabelToken(item.colorCode) + ); } cartItemColorHex(item: ShopCartItem): string { - return item.shopVariantColorHex || '#c9ced6'; + return ( + item.shopVariantColorHex || + findColorHex(item.shopVariantColorName) || + findColorHex(item.colorCode) || + '#c9ced6' + ); } trackByCartItem(_index: number, item: ShopCartItem): string { diff --git a/frontend/src/app/features/checkout/checkout.component.html b/frontend/src/app/features/checkout/checkout.component.html index 0b5d5dd..e76ef86 100644 --- a/frontend/src/app/features/checkout/checkout.component.html +++ b/frontend/src/app/features/checkout/checkout.component.html @@ -265,14 +265,16 @@ {{ "SHOP.VARIANT" | translate }}: - {{ variantLabel }} + {{ variantLabel | translate }} - {{ itemColorLabel(item) }} + {{ + itemColorLabel(item) | translate + }}
diff --git a/frontend/src/app/features/checkout/checkout.component.ts b/frontend/src/app/features/checkout/checkout.component.ts index c00abc7..77f7060 100644 --- a/frontend/src/app/features/checkout/checkout.component.ts +++ b/frontend/src/app/features/checkout/checkout.component.ts @@ -22,7 +22,12 @@ import { } from '../../shared/components/price-breakdown/price-breakdown.component'; import { LanguageService } from '../../core/services/language.service'; import { StlViewerComponent } from '../../shared/components/stl-viewer/stl-viewer.component'; -import { getColorHex } from '../../core/constants/colors.const'; +import { + findColorHex, + getColorHex, + getColorLabelToken, + normalizeColorValue, +} from '../../core/constants/colors.const'; @Component({ selector: 'app-checkout', @@ -252,8 +257,7 @@ export class CheckoutComponent implements OnInit { if (variantLabel) { return variantLabel; } - const colorName = String(item?.shopVariantColorName ?? '').trim(); - return colorName || null; + return getColorLabelToken(item?.shopVariantColorName); } showItemMaterial(item: any): boolean { @@ -284,10 +288,10 @@ export class CheckoutComponent implements OnInit { itemColorLabel(item: any): string { const shopColor = String(item?.shopVariantColorName ?? '').trim(); if (shopColor) { - return shopColor; + return getColorLabelToken(shopColor) ?? '-'; } const raw = String(item?.colorCode ?? '').trim(); - return raw || '-'; + return getColorLabelToken(raw) ?? '-'; } itemColorSwatch(item: any): string { @@ -310,12 +314,12 @@ export class CheckoutComponent implements OnInit { return raw; } - const byName = this.variantHexByColorName.get(raw.toLowerCase()); + const byName = this.variantHexByColorName.get(normalizeColorValue(raw)); if (byName) { return byName; } - const fallback = getColorHex(raw); + const fallback = findColorHex(raw) ?? getColorHex(raw); if (fallback && fallback !== '#facf0a') { return fallback; } @@ -373,7 +377,10 @@ export class CheckoutComponent implements OnInit { this.variantHexById.set(variantId, colorHex); } if (colorName && colorHex) { - this.variantHexByColorName.set(colorName.toLowerCase(), colorHex); + this.variantHexByColorName.set( + normalizeColorValue(colorName), + colorHex, + ); } } } diff --git a/frontend/src/app/features/order/order.component.html b/frontend/src/app/features/order/order.component.html index 59cbdeb..ac7d314 100644 --- a/frontend/src/app/features/order/order.component.html +++ b/frontend/src/app/features/order/order.component.html @@ -248,7 +248,8 @@ }} - {{ "SHOP.VARIANT" | translate }}: {{ variantLabel }} + {{ "SHOP.VARIANT" | translate }}: + {{ variantLabel | translate }} - {{ itemColorLabel(item) }} + {{ itemColorLabel(item) | translate }}
diff --git a/frontend/src/app/features/order/order.component.ts b/frontend/src/app/features/order/order.component.ts index 09d49ff..cee461e 100644 --- a/frontend/src/app/features/order/order.component.ts +++ b/frontend/src/app/features/order/order.component.ts @@ -6,6 +6,10 @@ import { AppCardComponent } from '../../shared/components/app-card/app-card.comp import { QuoteEstimatorService } from '../calculator/services/quote-estimator.service'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { environment } from '../../../environments/environment'; +import { + findColorHex, + getColorLabelToken, +} from '../../core/constants/colors.const'; import { PriceBreakdownComponent, PriceBreakdownRow, @@ -278,23 +282,28 @@ export class OrderComponent implements OnInit { return variantLabel; } - const colorName = String(item?.shopVariantColorName ?? '').trim(); - return colorName || null; + return getColorLabelToken(item?.shopVariantColorName); } itemColorLabel(item: PublicOrderItem): string { const shopColor = String(item?.shopVariantColorName ?? '').trim(); if (shopColor) { - return shopColor; + return getColorLabelToken(shopColor) ?? this.translate.instant('ORDER.NOT_AVAILABLE'); } const filamentColor = String(item?.filamentColorName ?? '').trim(); if (filamentColor) { - return filamentColor; + return ( + getColorLabelToken(filamentColor) ?? + this.translate.instant('ORDER.NOT_AVAILABLE') + ); } const rawColor = String(item?.colorCode ?? '').trim(); - return rawColor || this.translate.instant('ORDER.NOT_AVAILABLE'); + return ( + getColorLabelToken(rawColor) ?? + this.translate.instant('ORDER.NOT_AVAILABLE') + ); } itemColorHex(item: PublicOrderItem): string | null { @@ -313,7 +322,7 @@ export class OrderComponent implements OnInit { return rawColor; } - return null; + return findColorHex(rawColor); } showItemMaterial(item: PublicOrderItem): boolean { diff --git a/frontend/src/app/features/shop/product-detail.component.html b/frontend/src/app/features/shop/product-detail.component.html index 65aaf4e..96439d6 100644 --- a/frontend/src/app/features/shop/product-detail.component.html +++ b/frontend/src/app/features/shop/product-detail.component.html @@ -152,7 +152,7 @@ @if (selectedMaterial()?.label) { } - {{ colorLabel(activeVariant) }} + {{ colorLabel(activeVariant) | translate }} }

} @@ -267,7 +267,9 @@ - {{ colorLabel(activeVariant) }} + {{ + colorLabel(activeVariant) | translate + }} {{ selectedMaterial()?.label }} @@ -304,7 +306,7 @@ {{ - colorLabel(variant) + colorLabel(variant) | translate }} } diff --git a/frontend/src/app/features/shop/product-detail.component.ts b/frontend/src/app/features/shop/product-detail.component.ts index c74a32b..0ef11cc 100644 --- a/frontend/src/app/features/shop/product-detail.component.ts +++ b/frontend/src/app/features/shop/product-detail.component.ts @@ -15,7 +15,11 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { catchError, combineLatest, finalize, of, switchMap, tap } from 'rxjs'; import { SeoService } from '../../core/services/seo.service'; import { LanguageService } from '../../core/services/language.service'; -import { getColorHex } from '../../core/constants/colors.const'; +import { + findColorHex, + getColorHex, + getColorLabelToken, +} from '../../core/constants/colors.const'; import { AppButtonComponent } from '../../shared/components/app-button/app-button.component'; import { AppCardComponent } from '../../shared/components/app-card/app-card.component'; import { StlViewerComponent } from '../../shared/components/stl-viewer/stl-viewer.component'; @@ -403,7 +407,9 @@ export class ProductDetailComponent { } colorLabel(variant: ShopProductVariantOption): string { - return variant.colorName || variant.variantLabel || '-'; + return ( + getColorLabelToken(variant.colorName || variant.variantLabel) ?? '-' + ); } colorHex(variant: ShopProductVariantOption | null | undefined): string { @@ -524,17 +530,7 @@ export class ProductDetailComponent { } private colorHexFromName(value: string | null | undefined): string | null { - const colorName = String(value ?? '').trim(); - if (!colorName) { - return null; - } - - const fallback = getColorHex(colorName); - if (!fallback || fallback === '#facf0a') { - return null; - } - - return fallback; + return findColorHex(value); } private applySeo(product: ShopProductDetail): void { diff --git a/frontend/src/app/features/shop/shop-page.component.html b/frontend/src/app/features/shop/shop-page.component.html index 2471b43..e2950fe 100644 --- a/frontend/src/app/features/shop/shop-page.component.html +++ b/frontend/src/app/features/shop/shop-page.component.html @@ -84,7 +84,7 @@
{{ cartItemName(item) }} @if (cartItemVariant(item); as variant) { - {{ variant }} + {{ variant | translate }} } @if (cartItemColor(item); as color) { @@ -92,7 +92,7 @@ class="color-dot" [style.background-color]="cartItemColorHex(item)" > - {{ color }} + {{ color | translate }} }
diff --git a/frontend/src/app/features/shop/shop-page.component.ts b/frontend/src/app/features/shop/shop-page.component.ts index 3dbf4e9..90c1a19 100644 --- a/frontend/src/app/features/shop/shop-page.component.ts +++ b/frontend/src/app/features/shop/shop-page.component.ts @@ -22,6 +22,10 @@ import { } from 'rxjs'; import { SeoService } from '../../core/services/seo.service'; import { LanguageService } from '../../core/services/language.service'; +import { + findColorHex, + getColorLabelToken, +} from '../../core/constants/colors.const'; import { AppButtonComponent } from '../../shared/components/app-button/app-button.component'; import { AppCardComponent } from '../../shared/components/app-card/app-card.component'; import { ProductCardComponent } from './components/product-card/product-card.component'; @@ -157,15 +161,25 @@ export class ShopPageComponent { } cartItemVariant(item: ShopCartItem): string | null { - return item.shopVariantLabel || item.shopVariantColorName || null; + return ( + item.shopVariantLabel || getColorLabelToken(item.shopVariantColorName) + ); } cartItemColor(item: ShopCartItem): string | null { - return item.shopVariantColorName || item.colorCode || null; + return ( + getColorLabelToken(item.shopVariantColorName) ?? + getColorLabelToken(item.colorCode) + ); } cartItemColorHex(item: ShopCartItem): string { - return item.shopVariantColorHex || '#c9ced6'; + return ( + item.shopVariantColorHex || + findColorHex(item.shopVariantColorName) || + findColorHex(item.colorCode) || + '#c9ced6' + ); } navigateToCategory(slug?: string | null): void { diff --git a/frontend/src/app/shared/components/color-selector/color-selector.component.ts b/frontend/src/app/shared/components/color-selector/color-selector.component.ts index a45231b..1777686 100644 --- a/frontend/src/app/shared/components/color-selector/color-selector.component.ts +++ b/frontend/src/app/shared/components/color-selector/color-selector.component.ts @@ -4,6 +4,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { PRODUCT_COLORS, getColorHex, + getColorLabelToken, ColorCategory, ColorOption, } from '../../../core/constants/colors.const'; @@ -32,7 +33,7 @@ export class ColorSelectorComponent { const finish = v.finishType || 'AVAILABLE_COLORS'; const bucket = byFinish.get(finish) || []; bucket.push({ - label: v.colorName, + label: getColorLabelToken(v.colorName) ?? v.colorName, value: v.colorName, hex: v.hexColor, variantId: v.id, diff --git a/frontend/src/assets/i18n/de.json b/frontend/src/assets/i18n/de.json index 223fd36..cb0bbca 100644 --- a/frontend/src/assets/i18n/de.json +++ b/frontend/src/assets/i18n/de.json @@ -569,6 +569,13 @@ "BLUE": "Blau", "GREEN": "Grün", "YELLOW": "Gelb", + "ORANGE": "Orange", + "GRAY": "Grau", + "LIGHT_GRAY": "Hellgrau", + "DARK_GRAY": "Dunkelgrau", + "PURPLE": "Lila", + "BEIGE": "Beige", + "SAND_BEIGE": "Sandbeige", "MATTE_BLACK": "Matt Schwarz", "MATTE_WHITE": "Matt Weiß", "MATTE_GRAY": "Matt Grau" diff --git a/frontend/src/assets/i18n/en.json b/frontend/src/assets/i18n/en.json index 8f8ffbe..600c48d 100644 --- a/frontend/src/assets/i18n/en.json +++ b/frontend/src/assets/i18n/en.json @@ -569,6 +569,13 @@ "BLUE": "Blue", "GREEN": "Green", "YELLOW": "Yellow", + "ORANGE": "Orange", + "GRAY": "Gray", + "LIGHT_GRAY": "Light Gray", + "DARK_GRAY": "Dark Gray", + "PURPLE": "Purple", + "BEIGE": "Beige", + "SAND_BEIGE": "Sand Beige", "MATTE_BLACK": "Matte Black", "MATTE_WHITE": "Matte White", "MATTE_GRAY": "Matte Gray" diff --git a/frontend/src/assets/i18n/fr.json b/frontend/src/assets/i18n/fr.json index e3e80ab..6d27ad9 100644 --- a/frontend/src/assets/i18n/fr.json +++ b/frontend/src/assets/i18n/fr.json @@ -575,6 +575,13 @@ "BLUE": "Bleu", "GREEN": "Vert", "YELLOW": "Jaune", + "ORANGE": "Orange", + "GRAY": "Gris", + "LIGHT_GRAY": "Gris clair", + "DARK_GRAY": "Gris foncé", + "PURPLE": "Violet", + "BEIGE": "Beige", + "SAND_BEIGE": "Beige sable", "MATTE_BLACK": "Noir mat", "MATTE_WHITE": "Blanc mat", "MATTE_GRAY": "Gris mat" diff --git a/frontend/src/assets/i18n/it.json b/frontend/src/assets/i18n/it.json index 006e9ff..13ccb71 100644 --- a/frontend/src/assets/i18n/it.json +++ b/frontend/src/assets/i18n/it.json @@ -624,6 +624,13 @@ "BLUE": "Blu", "GREEN": "Verde", "YELLOW": "Giallo", + "ORANGE": "Arancione", + "GRAY": "Grigio", + "LIGHT_GRAY": "Grigio chiaro", + "DARK_GRAY": "Grigio scuro", + "PURPLE": "Viola", + "BEIGE": "Beige", + "SAND_BEIGE": "Beige sabbia", "MATTE_BLACK": "Nero opaco", "MATTE_WHITE": "Bianco opaco", "MATTE_GRAY": "Grigio opaco"