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 @@
}
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"