dev #49
@@ -1,6 +1,6 @@
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@if (siteIntroState() !== 'hidden') {
|
||||
@if (siteIntroState() !== "hidden") {
|
||||
<div
|
||||
class="site-intro"
|
||||
[class.site-intro--closing]="siteIntroState() === 'closing'"
|
||||
|
||||
@@ -54,7 +54,9 @@ export function parseAcceptLanguage(
|
||||
}
|
||||
|
||||
const qualityParam = params.find((param) => param.startsWith('q='));
|
||||
const quality = qualityParam ? Number.parseFloat(qualityParam.slice(2)) : 1;
|
||||
const quality = qualityParam
|
||||
? Number.parseFloat(qualityParam.slice(2))
|
||||
: 1;
|
||||
return {
|
||||
tag: rawTag,
|
||||
quality: Number.isFinite(quality) ? quality : 0,
|
||||
@@ -70,7 +72,9 @@ export function parseAcceptLanguage(
|
||||
index: number;
|
||||
} => entry !== null && entry.quality > 0,
|
||||
)
|
||||
.sort((left, right) => right.quality - left.quality || left.index - right.index)
|
||||
.sort(
|
||||
(left, right) => right.quality - left.quality || left.index - right.index,
|
||||
)
|
||||
.map((entry) => entry.tag);
|
||||
}
|
||||
|
||||
@@ -102,9 +106,7 @@ function resolveExplicitLanguageFromUrl(
|
||||
const normalizedUrl = String(url ?? '/');
|
||||
const [pathAndQuery] = normalizedUrl.split('#', 1);
|
||||
const [rawPath, rawQuery] = pathAndQuery.split('?', 2);
|
||||
const firstSegment = rawPath
|
||||
.split('/')
|
||||
.filter(Boolean)[0];
|
||||
const firstSegment = rawPath.split('/').filter(Boolean)[0];
|
||||
const pathLanguage = normalizeSupportedLanguage(firstSegment);
|
||||
if (pathLanguage) {
|
||||
return pathLanguage;
|
||||
|
||||
@@ -14,7 +14,10 @@ type SupportedLang = 'it' | 'en' | 'de' | 'fr';
|
||||
const FALLBACK_LANG: SupportedLang = 'it';
|
||||
const translationCache = new Map<SupportedLang, Promise<TranslationObject>>();
|
||||
|
||||
const translationLoaders: Record<SupportedLang, () => Promise<TranslationObject>> = {
|
||||
const translationLoaders: Record<
|
||||
SupportedLang,
|
||||
() => Promise<TranslationObject>
|
||||
> = {
|
||||
it: () =>
|
||||
import('../../../assets/i18n/it.json').then(
|
||||
(module) => module.default as TranslationObject,
|
||||
@@ -51,8 +54,9 @@ export class StaticTranslateLoader implements TranslateLoader {
|
||||
}
|
||||
|
||||
private loadTranslation(lang: SupportedLang): Promise<TranslationObject> {
|
||||
const transferStateKey =
|
||||
makeStateKey<TranslationObject>(`i18n:${lang.toLowerCase()}`);
|
||||
const transferStateKey = makeStateKey<TranslationObject>(
|
||||
`i18n:${lang.toLowerCase()}`,
|
||||
);
|
||||
if (
|
||||
isPlatformBrowser(this.platformId) &&
|
||||
this.transferState.hasKey(transferStateKey)
|
||||
|
||||
@@ -10,5 +10,4 @@ import { FooterComponent } from './footer.component';
|
||||
templateUrl: './layout.component.html',
|
||||
styleUrl: './layout.component.scss',
|
||||
})
|
||||
export class LayoutComponent {
|
||||
}
|
||||
export class LayoutComponent {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
||||
import { CommonModule, NgOptimizedImage } from '@angular/common';
|
||||
import {
|
||||
afterNextRender,
|
||||
Component,
|
||||
@@ -30,7 +30,13 @@ import {
|
||||
@Component({
|
||||
selector: 'app-navbar',
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterLink, RouterLinkActive, TranslateModule, NgOptimizedImage],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterLink,
|
||||
RouterLinkActive,
|
||||
TranslateModule,
|
||||
NgOptimizedImage,
|
||||
],
|
||||
templateUrl: './navbar.component.html',
|
||||
styleUrls: ['./navbar.component.scss'],
|
||||
})
|
||||
|
||||
@@ -67,7 +67,10 @@ export class LanguageService {
|
||||
}
|
||||
|
||||
const currentTree = this.router.parseUrl(this.router.url);
|
||||
const localizedRoute = this.resolveLocalizedRouteOverride(currentTree, lang);
|
||||
const localizedRoute = this.resolveLocalizedRouteOverride(
|
||||
currentTree,
|
||||
lang,
|
||||
);
|
||||
if (localizedRoute) {
|
||||
this.navigateToLocalizedRoute(currentTree, localizedRoute);
|
||||
return;
|
||||
@@ -220,7 +223,9 @@ export class LanguageService {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentPath = this.getCleanPath(this.router.serializeUrl(currentTree));
|
||||
const currentPath = this.getCleanPath(
|
||||
this.router.serializeUrl(currentTree),
|
||||
);
|
||||
const paths = Object.values(overrides)
|
||||
.map((path) => this.normalizeLocalizedRoutePath(path))
|
||||
.filter((path): path is string => !!path);
|
||||
|
||||
@@ -360,18 +360,23 @@ export class SeoService {
|
||||
}, {});
|
||||
}
|
||||
|
||||
private normalizeAlternatePaths(paths: SeoMap | null | undefined): SeoMap | null {
|
||||
private normalizeAlternatePaths(
|
||||
paths: SeoMap | null | undefined,
|
||||
): SeoMap | null {
|
||||
if (!paths) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = this.supportedLangs.reduce<SeoMap>((accumulator, lang) => {
|
||||
const normalized = this.supportedLangs.reduce<SeoMap>(
|
||||
(accumulator, lang) => {
|
||||
const path = this.normalizeSeoPath(paths[lang]);
|
||||
if (path) {
|
||||
accumulator[lang] = path;
|
||||
}
|
||||
return accumulator;
|
||||
}, {});
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
return Object.keys(normalized).length > 0 ? normalized : null;
|
||||
}
|
||||
@@ -445,7 +450,10 @@ export class SeoService {
|
||||
if (!path) {
|
||||
continue;
|
||||
}
|
||||
this.appendAlternateLink(this.seoLocaleByLang[alt], this.toAbsoluteUrl(path));
|
||||
this.appendAlternateLink(
|
||||
this.seoLocaleByLang[alt],
|
||||
this.toAbsoluteUrl(path),
|
||||
);
|
||||
}
|
||||
if (xDefaultPath) {
|
||||
this.appendAlternateLink('x-default', this.toAbsoluteUrl(xDefaultPath));
|
||||
|
||||
@@ -18,10 +18,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="animation-stage"
|
||||
[attr.data-variant]="variant()"
|
||||
>
|
||||
<div class="animation-stage" [attr.data-variant]="variant()">
|
||||
<app-brand-animation-logo
|
||||
[variant]="variant()"
|
||||
[decorative]="false"
|
||||
|
||||
@@ -774,9 +774,12 @@ export class ProductDetailComponent {
|
||||
const targetPath =
|
||||
product.localizedPaths?.[lang] ??
|
||||
`/${lang}/shop/p/${this.shopRouteService.productPathSegment(product)}`;
|
||||
const normalizedTargetPath =
|
||||
targetPath.startsWith('/') ? targetPath : `/${targetPath}`;
|
||||
const currentPath = this.router.serializeUrl(currentTree).split(/[?#]/, 1)[0];
|
||||
const normalizedTargetPath = targetPath.startsWith('/')
|
||||
? targetPath
|
||||
: `/${targetPath}`;
|
||||
const currentPath = this.router
|
||||
.serializeUrl(currentTree)
|
||||
.split(/[?#]/, 1)[0];
|
||||
if (currentPath === normalizedTargetPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -268,7 +268,9 @@ describe('ShopService', () => {
|
||||
});
|
||||
catalogRequest.flush(buildCatalog());
|
||||
|
||||
httpMock.expectNone('http://localhost:8000/api/shop/products/desk-cable-clip');
|
||||
httpMock.expectNone(
|
||||
'http://localhost:8000/api/shop/products/desk-cable-clip',
|
||||
);
|
||||
expect(errorResponse?.status).toBe(404);
|
||||
});
|
||||
|
||||
@@ -291,7 +293,9 @@ describe('ShopService', () => {
|
||||
});
|
||||
catalogRequest.flush(buildCatalog());
|
||||
|
||||
httpMock.expectNone('http://localhost:8000/api/shop/products/desk-cable-clip');
|
||||
httpMock.expectNone(
|
||||
'http://localhost:8000/api/shop/products/desk-cable-clip',
|
||||
);
|
||||
expect(errorResponse?.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -292,7 +292,8 @@ export class ShopService {
|
||||
|
||||
return this.getProductCatalog().pipe(
|
||||
map((catalog) =>
|
||||
catalog.products.find((product) =>
|
||||
catalog.products.find(
|
||||
(product) =>
|
||||
this.normalizePublicPath(product.publicPath) === normalizedPath,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -29,14 +29,14 @@
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.brand-animation[data-variant='site-intro'] .brand-animation__letter {
|
||||
animation: site-intro-preview var(--brand-animation-site-intro-duration, 1s) linear 1 forwards;
|
||||
.brand-animation[data-variant="site-intro"] .brand-animation__letter {
|
||||
animation: site-intro-preview var(--brand-animation-site-intro-duration, 1s)
|
||||
linear 1 forwards;
|
||||
}
|
||||
|
||||
.brand-animation[data-variant='calculator-loader'] .brand-animation__letter {
|
||||
.brand-animation[data-variant="calculator-loader"] .brand-animation__letter {
|
||||
animation: calculator-loader-loop
|
||||
var(--brand-animation-loader-loop-duration, 2.65s)
|
||||
infinite;
|
||||
var(--brand-animation-loader-loop-duration, 2.65s) infinite;
|
||||
}
|
||||
|
||||
@keyframes site-intro-preview {
|
||||
@@ -75,8 +75,7 @@
|
||||
var(--three-anchor-x) * var(--word-scale) * var(--word-spacing-factor)
|
||||
)
|
||||
)
|
||||
scaleX(var(--loader-group-scale-x))
|
||||
scaleY(var(--loader-group-scale-y));
|
||||
scaleX(var(--loader-group-scale-x)) scaleY(var(--loader-group-scale-y));
|
||||
}
|
||||
|
||||
12% {
|
||||
@@ -87,8 +86,7 @@
|
||||
var(--three-anchor-x) * var(--word-scale) * var(--word-spacing-factor)
|
||||
)
|
||||
)
|
||||
scaleX(var(--loader-group-scale-x))
|
||||
scaleY(var(--loader-group-scale-y));
|
||||
scaleX(var(--loader-group-scale-x)) scaleY(var(--loader-group-scale-y));
|
||||
}
|
||||
|
||||
12% {
|
||||
@@ -118,8 +116,7 @@
|
||||
var(--bee-anchor-x) * var(--word-scale) * var(--word-spacing-factor)
|
||||
)
|
||||
)
|
||||
scaleX(var(--loader-group-scale-x))
|
||||
scaleY(var(--loader-group-scale-y));
|
||||
scaleX(var(--loader-group-scale-x)) scaleY(var(--loader-group-scale-y));
|
||||
}
|
||||
|
||||
88% {
|
||||
@@ -131,12 +128,11 @@
|
||||
transform: translate(-50%, -50%)
|
||||
translateX(
|
||||
calc(
|
||||
(var(--bee-anchor-x) + var(--loader-exit-shift)) *
|
||||
var(--word-scale) * var(--word-spacing-factor)
|
||||
(var(--bee-anchor-x) + var(--loader-exit-shift)) * var(--word-scale) *
|
||||
var(--word-spacing-factor)
|
||||
)
|
||||
)
|
||||
scaleX(0.98)
|
||||
scaleY(1.02);
|
||||
scaleX(0.98) scaleY(1.02);
|
||||
}
|
||||
|
||||
94.01%,
|
||||
@@ -148,8 +144,7 @@
|
||||
var(--three-anchor-x) * var(--word-scale) * var(--word-spacing-factor)
|
||||
)
|
||||
)
|
||||
scaleX(var(--loader-group-scale-x))
|
||||
scaleY(var(--loader-group-scale-y));
|
||||
scaleX(var(--loader-group-scale-x)) scaleY(var(--loader-group-scale-y));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,8 +62,7 @@ export class BrandAnimationLogoComponent {
|
||||
readonly resolvedLetters = computed<ResolvedAnimationLetter[]>(() =>
|
||||
LETTERS.map((letter) => ({
|
||||
key: letter.key,
|
||||
src:
|
||||
this.variant() === 'site-intro' ? letter.yellowSrc : letter.darkSrc,
|
||||
src: this.variant() === 'site-intro' ? letter.yellowSrc : letter.darkSrc,
|
||||
wordX: letter.wordX,
|
||||
})),
|
||||
);
|
||||
|
||||
@@ -20,9 +20,9 @@ describe('server routing redirects', () => {
|
||||
});
|
||||
|
||||
it('redirects legacy shop product aliases to the canonical product route', () => {
|
||||
expect(resolvePublicRedirectTarget('/shop/accessories/desk-cable-clip')).toBe(
|
||||
'/it/shop/p/desk-cable-clip',
|
||||
);
|
||||
expect(
|
||||
resolvePublicRedirectTarget('/shop/accessories/desk-cable-clip'),
|
||||
).toBe('/it/shop/p/desk-cable-clip');
|
||||
expect(
|
||||
resolvePublicRedirectTarget('/de/shop/zubehor/schreibtisch-kabelhalter'),
|
||||
).toBe('/de/shop/p/schreibtisch-kabelhalter');
|
||||
|
||||
@@ -99,5 +99,7 @@ function splitSegments(pathname: string): string[] {
|
||||
}
|
||||
|
||||
function looksLikeLangToken(segment: string | null | undefined): boolean {
|
||||
return typeof segment === 'string' && /^[a-z]{2}(?:-[a-z]{2})?$/i.test(segment);
|
||||
return (
|
||||
typeof segment === 'string' && /^[a-z]{2}(?:-[a-z]{2})?$/i.test(segment)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user