fix(front-end): seo translated

This commit is contained in:
2026-03-14 15:02:00 +01:00
parent 2e68105da4
commit 576380e9a0
34 changed files with 944 additions and 135 deletions

View File

@@ -6,9 +6,8 @@ export const ABOUT_ROUTES: Routes = [
path: '',
component: AboutPageComponent,
data: {
seoTitle: 'Chi siamo | 3D fab',
seoDescription:
'Siamo un laboratorio di stampa 3D orientato a prototipi, ricambi e produzioni su misura.',
seoTitleKey: 'SEO.ROUTES.ABOUT.TITLE',
seoDescriptionKey: 'SEO.ROUTES.ABOUT.DESCRIPTION',
},
},
];

View File

@@ -8,9 +8,8 @@ export const CALCULATOR_ROUTES: Routes = [
component: CalculatorPageComponent,
data: {
mode: 'easy',
seoTitle: 'Calcolatore stampa 3D base | 3D fab',
seoDescription:
'Calcola rapidamente il prezzo della tua stampa 3D in modalita base.',
seoTitleKey: 'SEO.ROUTES.CALCULATOR.BASIC.TITLE',
seoDescriptionKey: 'SEO.ROUTES.CALCULATOR.BASIC.DESCRIPTION',
},
},
{
@@ -18,9 +17,8 @@ export const CALCULATOR_ROUTES: Routes = [
component: CalculatorPageComponent,
data: {
mode: 'advanced',
seoTitle: 'Calcolatore stampa 3D avanzato | 3D fab',
seoDescription:
'Configura parametri avanzati e ottieni un preventivo preciso con slicing reale.',
seoTitleKey: 'SEO.ROUTES.CALCULATOR.ADVANCED.TITLE',
seoDescriptionKey: 'SEO.ROUTES.CALCULATOR.ADVANCED.DESCRIPTION',
},
},
];

View File

@@ -101,7 +101,11 @@
<p class="upload-privacy-note">
{{ "LEGAL.CONSENT.UPLOAD_NOTICE_PREFIX" | translate }}
<a href="/privacy" target="_blank" rel="noopener">{{
<a
[href]="languageService.localizedPath('/privacy')"
target="_blank"
rel="noopener"
>{{
"LEGAL.CONSENT.UPLOAD_NOTICE_LINK" | translate
}}</a
>.

View File

@@ -30,6 +30,7 @@ import {
VariantOption,
} from '../../services/quote-estimator.service';
import { getColorHex } from '../../../../core/constants/colors.const';
import { LanguageService } from '../../../../core/services/language.service';
interface FormItem {
file: File;
@@ -106,6 +107,7 @@ export class UploadFormComponent implements OnInit {
private estimator = inject(QuoteEstimatorService);
private fb = inject(FormBuilder);
private translate = inject(TranslateService);
readonly languageService = inject(LanguageService);
form: FormGroup;

View File

@@ -120,11 +120,19 @@
<input type="checkbox" formControlName="acceptLegal" />
<span>
{{ "LEGAL.CONSENT.LABEL_PREFIX" | translate }}
<a href="/terms" target="_blank" rel="noopener">{{
<a
[href]="languageService.localizedPath('/terms')"
target="_blank"
rel="noopener"
>{{
"LEGAL.CONSENT.TERMS_LINK" | translate
}}</a>
{{ "LEGAL.CONSENT.AND" | translate }}
<a href="/privacy" target="_blank" rel="noopener">{{
<a
[href]="languageService.localizedPath('/privacy')"
target="_blank"
rel="noopener"
>{{
"LEGAL.CONSENT.PRIVACY_LINK" | translate
}}</a
>.

View File

@@ -1,4 +1,4 @@
import { Component, input, output, signal } from '@angular/core';
import { Component, inject, input, output, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
ReactiveFormsModule,
@@ -11,6 +11,7 @@ import { AppCardComponent } from '../../../../shared/components/app-card/app-car
import { AppInputComponent } from '../../../../shared/components/app-input/app-input.component';
import { AppButtonComponent } from '../../../../shared/components/app-button/app-button.component';
import { QuoteResult } from '../../services/quote-estimator.service';
import { LanguageService } from '../../../../core/services/language.service';
@Component({
selector: 'app-user-details',
@@ -30,6 +31,7 @@ export class UserDetailsComponent {
quote = input<QuoteResult>();
submitOrder = output<any>();
cancel = output<void>();
readonly languageService = inject(LanguageService);
form: FormGroup;
submitting = signal(false);

View File

@@ -2,9 +2,9 @@
<div class="container ui-page-hero ui-page-hero--spacious checkout-hero">
<h1 class="ui-page-title">{{ "CHECKOUT.TITLE" | translate }}</h1>
<p class="ui-page-subtitle cad-subtitle" *ngIf="isCadSession()">
Servizio CAD
{{ "CHECKOUT.CAD_SERVICE" | translate }}
<ng-container *ngIf="cadRequestId()">
riferito alla richiesta contatto #{{ cadRequestId() }}
{{ "CHECKOUT.CAD_REQUEST_REF" | translate: { id: cadRequestId() } }}
</ng-container>
</p>
</div>
@@ -204,11 +204,19 @@
<span class="ui-checkbox__mark"></span>
<span>
{{ "LEGAL.CONSENT.LABEL_PREFIX" | translate }}
<a href="/terms" target="_blank" rel="noopener">{{
<a
[href]="languageService.localizedPath('/terms')"
target="_blank"
rel="noopener"
>{{
"LEGAL.CONSENT.TERMS_LINK" | translate
}}</a>
{{ "LEGAL.CONSENT.AND" | translate }}
<a href="/privacy" target="_blank" rel="noopener">{{
<a
[href]="languageService.localizedPath('/privacy')"
target="_blank"
rel="noopener"
>{{
"LEGAL.CONSENT.PRIVACY_LINK" | translate
}}</a
>.
@@ -329,7 +337,9 @@
</div>
<div class="summary-item cad-summary-item" *ngIf="cadTotal() > 0">
<div class="item-details">
<span class="item-name">Servizio CAD</span>
<span class="item-name">{{
"CHECKOUT.CAD_SERVICE" | translate
}}</span>
<div class="item-specs-sub">{{ cadHours() }}h</div>
</div>
<div class="item-price">

View File

@@ -51,7 +51,7 @@ export class CheckoutComponent implements OnInit {
private quoteService = inject(QuoteEstimatorService);
private router = inject(Router);
private route = inject(ActivatedRoute);
private languageService = inject(LanguageService);
readonly languageService = inject(LanguageService);
checkoutForm: FormGroup;
sessionId: string | null = null;
@@ -147,7 +147,7 @@ export class CheckoutComponent implements OnInit {
this.sessionId = params['session'];
if (!this.sessionId) {
this.error = 'CHECKOUT.ERR_NO_SESSION_START';
this.router.navigate(['/']); // Redirect if no session
this.router.navigate(['/', this.languageService.selectedLang()]);
return;
}

View File

@@ -85,7 +85,11 @@
<p class="ui-form-hint">{{ "CONTACT.UPLOAD_HINT" | translate }}</p>
<p class="ui-form-hint upload-privacy-note">
{{ "LEGAL.CONSENT.UPLOAD_NOTICE_PREFIX" | translate }}
<a href="/privacy" target="_blank" rel="noopener">{{
<a
[href]="languageService.localizedPath('/privacy')"
target="_blank"
rel="noopener"
>{{
"LEGAL.CONSENT.UPLOAD_NOTICE_LINK" | translate
}}</a
>.
@@ -161,11 +165,19 @@
<span class="ui-checkbox__mark"></span>
<span>
{{ "LEGAL.CONSENT.LABEL_PREFIX" | translate }}
<a href="/terms" target="_blank" rel="noopener">{{
<a
[href]="languageService.localizedPath('/terms')"
target="_blank"
rel="noopener"
>{{
"LEGAL.CONSENT.TERMS_LINK" | translate
}}</a>
{{ "LEGAL.CONSENT.AND" | translate }}
<a href="/privacy" target="_blank" rel="noopener">{{
<a
[href]="languageService.localizedPath('/privacy')"
target="_blank"
rel="noopener"
>{{
"LEGAL.CONSENT.PRIVACY_LINK" | translate
}}</a
>.

View File

@@ -70,7 +70,7 @@ export class ContactFormComponent implements OnDestroy {
];
private quoteRequestService = inject(QuoteRequestService);
private languageService = inject(LanguageService);
readonly languageService = inject(LanguageService);
constructor(
private fb: FormBuilder,

View File

@@ -6,9 +6,8 @@ export const CONTACT_ROUTES: Routes = [
loadComponent: () =>
import('./contact-page.component').then((m) => m.ContactPageComponent),
data: {
seoTitle: 'Contatti | 3D fab',
seoDescription:
'Richiedi informazioni, preventivi personalizzati o supporto per progetti di stampa 3D.',
seoTitleKey: 'SEO.ROUTES.CONTACT.TITLE',
seoDescriptionKey: 'SEO.ROUTES.CONTACT.DESCRIPTION',
},
},
];

View File

@@ -16,15 +16,27 @@
{{ "HOME.HERO_SUBTITLE" | translate }}
</p>
<div class="hero-actions ui-inline-actions ui-inline-actions--wide">
<app-button variant="primary" routerLink="/calculator/basic">{{
<app-button
variant="primary"
[routerLink]="languageService.localizedPath('/calculator/basic')"
>{{
"HOME.BTN_CALCULATE" | translate
}}</app-button>
<app-button variant="outline" routerLink="/shop">{{
}}</app-button
>
<app-button
variant="outline"
[routerLink]="languageService.localizedPath('/shop')"
>{{
"HOME.BTN_SHOP" | translate
}}</app-button>
<app-button variant="text" routerLink="/contact">{{
}}</app-button
>
<app-button
variant="text"
[routerLink]="languageService.localizedPath('/contact')"
>{{
"HOME.BTN_CONTACT" | translate
}}</app-button>
}}</app-button
>
</div>
</div>
<aside class="hero-swiss-card">
@@ -136,13 +148,13 @@
<app-button
variant="primary"
[fullWidth]="true"
routerLink="/calculator/basic"
[routerLink]="languageService.localizedPath('/calculator/basic')"
>{{ "HOME.BTN_OPEN_CALC" | translate }}</app-button
>
<app-button
variant="outline"
[fullWidth]="true"
routerLink="/contact"
[routerLink]="languageService.localizedPath('/contact')"
>{{ "HOME.BTN_CONTACT" | translate }}</app-button
>
</div>
@@ -167,12 +179,20 @@
<li>{{ "HOME.SEC_SHOP_LIST_3" | translate }}</li>
</ul>
<div class="shop-actions ui-inline-actions">
<app-button variant="primary" routerLink="/shop">{{
<app-button
variant="primary"
[routerLink]="languageService.localizedPath('/shop')"
>{{
"HOME.BTN_DISCOVER" | translate
}}</app-button>
<app-button variant="outline" routerLink="/contact">{{
}}</app-button
>
<app-button
variant="outline"
[routerLink]="languageService.localizedPath('/contact')"
>{{
"HOME.BTN_REQ_SOLUTION" | translate
}}</app-button>
}}</app-button
>
</div>
</div>
<div
@@ -237,12 +257,20 @@
{{ "HOME.SEC_ABOUT_TEXT" | translate }}
</p>
<div class="about-actions ui-inline-actions">
<app-button variant="primary" routerLink="/about">{{
<app-button
variant="primary"
[routerLink]="languageService.localizedPath('/about')"
>{{
"HOME.SEC_ABOUT_TITLE" | translate
}}</app-button>
<app-button variant="outline" routerLink="/contact">{{
}}</app-button
>
<app-button
variant="outline"
[routerLink]="languageService.localizedPath('/contact')"
>{{
"HOME.BTN_CONTACT" | translate
}}</app-button>
}}</app-button
>
</div>
</div>
<div class="about-media">

View File

@@ -5,6 +5,7 @@ import { RouterLink } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { AppButtonComponent } from '../../shared/components/app-button/app-button.component';
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
import { LanguageService } from '../../core/services/language.service';
import {
buildPublicMediaUsageScopeKey,
PublicMediaDisplayImage,
@@ -69,6 +70,7 @@ const HOME_CAPABILITY_CONFIGS: readonly HomeCapabilityConfig[] = [
})
export class HomeComponent {
private readonly publicMediaService = inject(PublicMediaService);
readonly languageService = inject(LanguageService);
private readonly mediaByUsage = toSignal(
this.publicMediaService.getUsageCollections([

View File

@@ -6,9 +6,8 @@ export const LEGAL_ROUTES: Routes = [
loadComponent: () =>
import('./privacy/privacy.component').then((m) => m.PrivacyComponent),
data: {
seoTitle: 'Privacy Policy | 3D fab',
seoDescription:
'Informativa privacy di 3D fab: trattamento dati, finalita e contatti.',
seoTitleKey: 'SEO.ROUTES.LEGAL.PRIVACY.TITLE',
seoDescriptionKey: 'SEO.ROUTES.LEGAL.PRIVACY.DESCRIPTION',
},
},
{
@@ -16,9 +15,8 @@ export const LEGAL_ROUTES: Routes = [
loadComponent: () =>
import('./terms/terms.component').then((m) => m.TermsComponent),
data: {
seoTitle: 'Termini e condizioni | 3D fab',
seoDescription:
'Termini e condizioni del servizio di stampa 3D e del calcolatore preventivi.',
seoTitleKey: 'SEO.ROUTES.LEGAL.TERMS.TITLE',
seoDescriptionKey: 'SEO.ROUTES.LEGAL.TERMS.DESCRIPTION',
},
},
];

View File

@@ -245,7 +245,9 @@ export class OrderComponent implements OnInit {
amount: order?.subtotalChf ?? 0,
},
{
label: `Servizio CAD (${order?.cadHours || 0}h)`,
label: this.translate.instant('ORDER.CAD_SERVICE', {
hours: order?.cadHours || 0,
}),
amount: order?.cadTotalChf ?? 0,
visible: (order?.cadTotalChf ?? 0) > 0,
},

View File

@@ -366,7 +366,7 @@ export class ProductDetailComponent {
if (!sessionId) {
return;
}
this.router.navigate(['/checkout'], {
this.router.navigate(['/', this.languageService.selectedLang(), 'checkout'], {
queryParams: { session: sessionId },
});
}

View File

@@ -239,7 +239,10 @@
</h2>
</div>
<app-button variant="primary" routerLink="/contact">
<app-button
variant="primary"
[routerLink]="languageService.localizedPath('/contact')"
>
{{ "NAV.CONTACT" | translate }}
</app-button>
</div>

View File

@@ -59,7 +59,7 @@ export class ShopPageComponent {
private readonly router = inject(Router);
private readonly translate = inject(TranslateService);
private readonly seoService = inject(SeoService);
private readonly languageService = inject(LanguageService);
readonly languageService = inject(LanguageService);
private readonly shopRouteService = inject(ShopRouteService);
readonly shopService = inject(ShopService);
@@ -242,7 +242,7 @@ export class ShopPageComponent {
if (!sessionId) {
return;
}
this.router.navigate(['/checkout'], {
this.router.navigate(['/', this.languageService.selectedLang(), 'checkout'], {
queryParams: {
session: sessionId,
},

View File

@@ -7,30 +7,32 @@ export const SHOP_ROUTES: Routes = [
path: '',
component: ShopPageComponent,
data: {
seoTitle: 'Shop 3D fab',
seoDescription:
'Catalogo prodotti stampati in 3D, accessori tecnici e soluzioni pratiche pronte all uso.',
seoTitleKey: 'SEO.ROUTES.SHOP.TITLE',
seoDescriptionKey: 'SEO.ROUTES.SHOP.DESCRIPTION',
},
},
{
path: 'p/:productSlug',
component: ProductDetailComponent,
data: {
seoTitle: 'Prodotto | 3D fab',
seoTitleKey: 'SEO.ROUTES.SHOP.PRODUCT_TITLE',
seoDescriptionKey: 'SEO.ROUTES.SHOP.PRODUCT_DESCRIPTION',
},
},
{
path: ':categorySlug/:productSlug',
component: ProductDetailComponent,
data: {
seoTitle: 'Prodotto | 3D fab',
seoTitleKey: 'SEO.ROUTES.SHOP.PRODUCT_TITLE',
seoDescriptionKey: 'SEO.ROUTES.SHOP.PRODUCT_DESCRIPTION',
},
},
{
path: ':categorySlug',
component: ShopPageComponent,
data: {
seoTitle: 'Categoria Shop | 3D fab',
seoTitleKey: 'SEO.ROUTES.SHOP.CATEGORY_TITLE',
seoDescriptionKey: 'SEO.ROUTES.SHOP.CATEGORY_DESCRIPTION',
},
},
];