added language routing system

This commit is contained in:
sdarbinyan
2026-02-26 22:23:08 +04:00
parent a4765ffe98
commit e4206d8abc
34 changed files with 197 additions and 98 deletions

View File

@@ -1,5 +1,6 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { brandInfoRoutes, brandLegalRoutes } from './brands/brand-routes'; import { brandInfoRoutes, brandLegalRoutes } from './brands/brand-routes';
import { languageGuard } from './guards/language.guard';
// Core routes (same across all brands) // Core routes (same across all brands)
const coreRoutes: Routes = [ const coreRoutes: Routes = [
@@ -29,13 +30,18 @@ const coreRoutes: Routes = [
} }
]; ];
// Combine core routes with brand-specific routes // All routes sit under a :lang prefix (e.g. /ru/cart, /en/item/5)
export const routes: Routes = [ export const routes: Routes = [
...coreRoutes,
...brandInfoRoutes,
...brandLegalRoutes,
{ {
path: '**', path: ':lang',
redirectTo: '' canActivate: [languageGuard],
} children: [
...coreRoutes,
...brandInfoRoutes,
...brandLegalRoutes,
{ path: '**', redirectTo: '' }
]
},
// URLs without a language prefix → redirect to default language
{ path: '**', redirectTo: 'ru' }
]; ];

View File

@@ -45,7 +45,8 @@ export class App implements OnInit {
.subscribe((event) => { .subscribe((event) => {
const navEnd = event as NavigationEnd; const navEnd = event as NavigationEnd;
const url = navEnd.urlAfterRedirects || navEnd.url; const url = navEnd.urlAfterRedirects || navEnd.url;
this.isHomePage.set(url === '/' || url === '/home' || url === ''); // Home pages: /ru, /en, /hy (with or without trailing slash)
this.isHomePage.set(/^\/[a-z]{2}\/?$/.test(url) || url === '/' || url === '');
}); });
} }

View File

@@ -103,7 +103,7 @@
<section class="info-card wide"> <section class="info-card wide">
<div class="card-icon">↩️</div> <div class="card-icon">↩️</div>
<h2>6. Возврат средств</h2> <h2>6. Возврат средств</h2>
<p>6.1. Порядок возврата денежных средств регулируется <a routerLink="/return-policy">Политикой возврата</a> и зависит от типа приобретенного Товара/Услуги.</p> <p>6.1. Порядок возврата денежных средств регулируется <a [routerLink]="'/return-policy' | langRoute">Политикой возврата</a> и зависит от типа приобретенного Товара/Услуги.</p>
<p>6.2. Возврат средств производится на тот же платежный инструмент, с которого была произведена оплата.</p> <p>6.2. Возврат средств производится на тот же платежный инструмент, с которого была произведена оплата.</p>
<p>6.3. Срок возврата денежных средств составляет:</p> <p>6.3. Срок возврата денежных средств составляет:</p>
<div class="refund-times"> <div class="refund-times">

View File

@@ -1,8 +1,10 @@
import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterLink } from '@angular/router';
import { LangRoutePipe } from '../../../../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-payment-terms-novo', selector: 'app-payment-terms-novo',
imports: [], imports: [RouterLink, LangRoutePipe],
templateUrl: './payment-terms.component.html', templateUrl: './payment-terms.component.html',
styleUrls: ['../../../../../pages/legal/payment-terms/payment-terms.component.scss'], styleUrls: ['../../../../../pages/legal/payment-terms/payment-terms.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -31,7 +31,7 @@
<p>1.4. Акцепт происходит автоматически при любом действии: визите, регистрации, оформлении покупки.</p> <p>1.4. Акцепт происходит автоматически при любом действии: визите, регистрации, оформлении покупки.</p>
<p>1.5. Подписание бумажного договора не требуется — электронная форма юридически действительна.</p> <p>1.5. Подписание бумажного договора не требуется — электронная форма юридически действительна.</p>
<p>1.6. Несогласие с условиями означает обязанность покинуть сайт.</p> <p>1.6. Несогласие с условиями означает обязанность покинуть сайт.</p>
<p>1.7. Также применяется <a routerLink="/privacy-policy">Политика конфиденциальности</a>.</p> <p>1.7. Также применяется <a [routerLink]="'/privacy-policy' | langRoute">Политика конфиденциальности</a>.</p>
<p>1.8. Мы можем обновлять условия в одностороннем порядке.</p> <p>1.8. Мы можем обновлять условия в одностороннем порядке.</p>
<p>1.9. Промо-кампании могут иметь специальные правила.</p> <p>1.9. Промо-кампании могут иметь специальные правила.</p>
</section> </section>
@@ -204,7 +204,7 @@
<section class="info-card wide"> <section class="info-card wide">
<div class="card-icon">↩️</div> <div class="card-icon">↩️</div>
<h2>13. Возврат и обмен товара</h2> <h2>13. Возврат и обмен товара</h2>
<p><strong>13.1. Общие правила:</strong> Цифровые товары не подлежат возврату. Физические товары — согласно <a routerLink="/return-policy">Политике возврата</a> и законам о правах потребителей.</p> <p><strong>13.1. Общие правила:</strong> Цифровые товары не подлежат возврату. Физические товары — согласно <a [routerLink]="'/return-policy' | langRoute">Политике возврата</a> и законам о правах потребителей.</p>
<p><strong>13.2. Процедура возврата:</strong> В соответствии с соглашением и законодательством РФ.</p> <p><strong>13.2. Процедура возврата:</strong> В соответствии с соглашением и законодательством РФ.</p>
<p><strong>13.3. Акционные наборы:</strong> Возврат только в комплексе, отдельные товары вернуть нельзя.</p> <p><strong>13.3. Акционные наборы:</strong> Возврат только в комплексе, отдельные товары вернуть нельзя.</p>
<p><strong>13.4. Затраты на доставку:</strong> При возврате качественного товара продавец может взыскать затраты на доставку.</p> <p><strong>13.4. Затраты на доставку:</strong> При возврате качественного товара продавец может взыскать затраты на доставку.</p>

View File

@@ -1,8 +1,10 @@
import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterLink } from '@angular/router';
import { LangRoutePipe } from '../../../../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-public-offer-novo', selector: 'app-public-offer-novo',
imports: [], imports: [RouterLink, LangRoutePipe],
templateUrl: './public-offer.component.html', templateUrl: './public-offer.component.html',
styleUrls: ['../../../../../pages/legal/public-offer/public-offer.component.scss'], styleUrls: ['../../../../../pages/legal/public-offer/public-offer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -11,27 +11,27 @@
<div class="novo-footer-col"> <div class="novo-footer-col">
<h4>Компания</h4> <h4>Компания</h4>
<ul class="novo-footer-links"> <ul class="novo-footer-links">
<li><a routerLink="/about">О нас</a></li> <li><a [routerLink]="'/about' | langRoute">О нас</a></li>
<li><a routerLink="/contacts">Контакты</a></li> <li><a [routerLink]="'/contacts' | langRoute">Контакты</a></li>
<li><a routerLink="/company-details">Реквизиты</a></li> <li><a [routerLink]="'/company-details' | langRoute">Реквизиты</a></li>
</ul> </ul>
</div> </div>
<div class="novo-footer-col"> <div class="novo-footer-col">
<h4>Поддержка</h4> <h4>Поддержка</h4>
<ul class="novo-footer-links"> <ul class="novo-footer-links">
<li><a routerLink="/faq">FAQ</a></li> <li><a [routerLink]="'/faq' | langRoute">FAQ</a></li>
<li><a routerLink="/delivery">Доставка</a></li> <li><a [routerLink]="'/delivery' | langRoute">Доставка</a></li>
<li><a routerLink="/guarantee">Гарантия</a></li> <li><a [routerLink]="'/guarantee' | langRoute">Гарантия</a></li>
</ul> </ul>
</div> </div>
<div class="novo-footer-col"> <div class="novo-footer-col">
<h4>Правовая информация</h4> <h4>Правовая информация</h4>
<ul class="novo-footer-links"> <ul class="novo-footer-links">
<li><a routerLink="/public-offer">Оферта</a></li> <li><a [routerLink]="'/public-offer' | langRoute">Оферта</a></li>
<li><a routerLink="/privacy-policy">Конфиденциальность</a></li> <li><a [routerLink]="'/privacy-policy' | langRoute">Конфиденциальность</a></li>
<li><a routerLink="/return-policy">Возврат</a></li> <li><a [routerLink]="'/return-policy' | langRoute">Возврат</a></li>
</ul> </ul>
</div> </div>
</div> </div>
@@ -63,28 +63,28 @@
<div class="dexar-footer-col"> <div class="dexar-footer-col">
<h4>Информация</h4> <h4>Информация</h4>
<ul> <ul>
<li><a routerLink="/about">О компании</a></li> <li><a [routerLink]="'/about' | langRoute">О компании</a></li>
<li><a routerLink="/contacts">Контакты</a></li> <li><a [routerLink]="'/contacts' | langRoute">Контакты</a></li>
<li><a routerLink="/company-details">Реквизиты</a></li> <li><a [routerLink]="'/company-details' | langRoute">Реквизиты</a></li>
</ul> </ul>
</div> </div>
<div class="dexar-footer-col"> <div class="dexar-footer-col">
<h4>Документы</h4> <h4>Документы</h4>
<ul> <ul>
<li><a routerLink="/payment-terms">Правила оплаты</a></li> <li><a [routerLink]="'/payment-terms' | langRoute">Правила оплаты</a></li>
<li><a routerLink="/return-policy">Политика возврата</a></li> <li><a [routerLink]="'/return-policy' | langRoute">Политика возврата</a></li>
<li><a routerLink="/public-offer">Публичная оферта</a></li> <li><a [routerLink]="'/public-offer' | langRoute">Публичная оферта</a></li>
<li><a routerLink="/privacy-policy">Конфиденциальность</a></li> <li><a [routerLink]="'/privacy-policy' | langRoute">Конфиденциальность</a></li>
</ul> </ul>
</div> </div>
<div class="dexar-footer-col"> <div class="dexar-footer-col">
<h4>Помощь</h4> <h4>Помощь</h4>
<ul> <ul>
<li><a routerLink="/faq">FAQ</a></li> <li><a [routerLink]="'/faq' | langRoute">FAQ</a></li>
<li><a routerLink="/delivery">Доставка</a></li> <li><a [routerLink]="'/delivery' | langRoute">Доставка</a></li>
<li><a routerLink="/guarantee">Гарантия</a></li> <li><a [routerLink]="'/guarantee' | langRoute">Гарантия</a></li>
</ul> </ul>
</div> </div>

View File

@@ -1,10 +1,11 @@
import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-footer', selector: 'app-footer',
imports: [RouterLink], imports: [RouterLink, LangRoutePipe],
templateUrl: './footer.component.html', templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss'], styleUrls: ['./footer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -3,7 +3,7 @@
<header class="novo-header"> <header class="novo-header">
<div class="novo-header-container"> <div class="novo-header-container">
<div class="novo-left"> <div class="novo-left">
<a routerLink="/" class="novo-logo" (click)="closeMenu()"> <a [routerLink]="'/' | langRoute" class="novo-logo" (click)="closeMenu()">
<app-logo /> <app-logo />
<!-- <span class="novo-brand">{{ brandName }}</span> --> <!-- <span class="novo-brand">{{ brandName }}</span> -->
</a> </a>
@@ -11,16 +11,16 @@
<nav class="novo-nav" [class.novo-nav-open]="menuOpen"> <nav class="novo-nav" [class.novo-nav-open]="menuOpen">
<div class="novo-nav-links"> <div class="novo-nav-links">
<a routerLink="/" routerLinkActive="novo-active" [routerLinkActiveOptions]="{exact: true}" (click)="closeMenu()" class="novo-link"> <a [routerLink]="'/' | langRoute" routerLinkActive="novo-active" [routerLinkActiveOptions]="{exact: true}" (click)="closeMenu()" class="novo-link">
Главная Главная
</a> </a>
<a routerLink="/search" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link"> <a [routerLink]="'/search' | langRoute" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link">
Поиск Поиск
</a> </a>
<a routerLink="/about" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link"> <a [routerLink]="'/about' | langRoute" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link">
О нас О нас
</a> </a>
<a routerLink="/contacts" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link"> <a [routerLink]="'/contacts' | langRoute" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link">
Контакты Контакты
</a> </a>
</div> </div>
@@ -29,7 +29,7 @@
<div class="novo-right"> <div class="novo-right">
<app-language-selector /> <app-language-selector />
<a routerLink="/cart" routerLinkActive="novo-cart-active" class="novo-cart" (click)="closeMenu()"> <a [routerLink]="'/cart' | langRoute" routerLinkActive="novo-cart-active" class="novo-cart" (click)="closeMenu()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="9" cy="21" r="1"></circle> <circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle> <circle cx="20" cy="21" r="1"></circle>
@@ -53,21 +53,21 @@
<header class="dexar-header"> <header class="dexar-header">
<div class="dexar-header-container"> <div class="dexar-header-container">
<!-- Logo --> <!-- Logo -->
<a routerLink="/" class="dexar-logo" (click)="closeMenu()"> <a [routerLink]="'/' | langRoute" class="dexar-logo" (click)="closeMenu()">
<app-logo /> <app-logo />
</a> </a>
<!-- Navigation Buttons (desktop) --> <!-- Navigation Buttons (desktop) -->
<nav class="dexar-nav"> <nav class="dexar-nav">
<div class="dexar-nav-group"> <div class="dexar-nav-group">
<a routerLink="/" routerLinkActive="dexar-active" [routerLinkActiveOptions]="{exact: true}" <a [routerLink]="'/' | langRoute" routerLinkActive="dexar-active" [routerLinkActiveOptions]="{exact: true}"
(click)="closeMenu()" class="dexar-nav-btn dexar-nav-btn-left"> (click)="closeMenu()" class="dexar-nav-btn dexar-nav-btn-left">
Главная Главная
</a> </a>
<a routerLink="/about" routerLinkActive="dexar-active" (click)="closeMenu()" class="dexar-nav-btn dexar-nav-btn-middle"> <a [routerLink]="'/about' | langRoute" routerLinkActive="dexar-active" (click)="closeMenu()" class="dexar-nav-btn dexar-nav-btn-middle">
О нас О нас
</a> </a>
<a routerLink="/contacts" routerLinkActive="dexar-active" (click)="closeMenu()" class="dexar-nav-btn dexar-nav-btn-right"> <a [routerLink]="'/contacts' | langRoute" routerLinkActive="dexar-active" (click)="closeMenu()" class="dexar-nav-btn dexar-nav-btn-right">
Контакты Контакты
</a> </a>
</div> </div>
@@ -95,7 +95,7 @@
<!-- Right Actions --> <!-- Right Actions -->
<div class="dexar-actions"> <div class="dexar-actions">
<!-- Cart Button --> <!-- Cart Button -->
<a routerLink="/cart" routerLinkActive="dexar-cart-active" class="dexar-cart-btn" (click)="closeMenu()"> <a [routerLink]="'/cart' | langRoute" routerLinkActive="dexar-cart-active" class="dexar-cart-btn" (click)="closeMenu()">
<svg width="32" height="24" viewBox="0 0 48 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="32" height="24" viewBox="0 0 48 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" fill="transparent" /> <path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" fill="transparent" />
<path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" stroke="#677B78" /> <path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" stroke="#677B78" />
@@ -128,7 +128,7 @@
<!-- Mobile Menu Panel (outside header for correct stacking) --> <!-- Mobile Menu Panel (outside header for correct stacking) -->
<div class="dexar-mobile-menu" [class.dexar-mobile-menu-open]="menuOpen"> <div class="dexar-mobile-menu" [class.dexar-mobile-menu-open]="menuOpen">
<a routerLink="/" routerLinkActive="dexar-mobile-active" [routerLinkActiveOptions]="{exact: true}" <a [routerLink]="'/' | langRoute" routerLinkActive="dexar-mobile-active" [routerLinkActiveOptions]="{exact: true}"
(click)="closeMenu()" class="dexar-mobile-item"> (click)="closeMenu()" class="dexar-mobile-item">
<svg width="24" height="24" viewBox="0 0 31 31" fill="none"> <svg width="24" height="24" viewBox="0 0 31 31" fill="none">
<path d="M16.185 2.22124C15.8067 1.84292 15.1933 1.84292 14.815 2.22124L3.18999 13.8462C3.00831 14.0279 2.90625 14.2743 2.90625 14.5312V28.0938C2.90625 28.6288 3.33997 29.0625 3.875 29.0625H12.5938C13.1288 29.0625 13.5625 28.6288 13.5625 28.0938V20.3438H17.4375V28.0938C17.4375 28.6288 17.8712 29.0625 18.4062 29.0625H27.125C27.66 29.0625 28.0938 28.6288 28.0938 28.0938V14.5312C28.0938 14.2743 27.9917 14.0279 27.81 13.8462L25.1875 11.2237V4.84375C25.1875 4.30872 24.7538 3.875 24.2188 3.875H22.2812C21.7462 3.875 21.3125 4.30872 21.3125 4.84375V7.34873L16.185 2.22124ZM4.84375 27.125V14.9325L15.5 4.27627L26.1562 14.9325V27.125H19.375V19.375C19.375 18.84 18.9413 18.4062 18.4062 18.4062H12.5938C12.0587 18.4062 11.625 18.84 11.625 19.375V27.125H4.84375Z" fill="#497671" /> <path d="M16.185 2.22124C15.8067 1.84292 15.1933 1.84292 14.815 2.22124L3.18999 13.8462C3.00831 14.0279 2.90625 14.2743 2.90625 14.5312V28.0938C2.90625 28.6288 3.33997 29.0625 3.875 29.0625H12.5938C13.1288 29.0625 13.5625 28.6288 13.5625 28.0938V20.3438H17.4375V28.0938C17.4375 28.6288 17.8712 29.0625 18.4062 29.0625H27.125C27.66 29.0625 28.0938 28.6288 28.0938 28.0938V14.5312C28.0938 14.2743 27.9917 14.0279 27.81 13.8462L25.1875 11.2237V4.84375C25.1875 4.30872 24.7538 3.875 24.2188 3.875H22.2812C21.7462 3.875 21.3125 4.30872 21.3125 4.84375V7.34873L16.185 2.22124ZM4.84375 27.125V14.9325L15.5 4.27627L26.1562 14.9325V27.125H19.375V19.375C19.375 18.84 18.9413 18.4062 18.4062 18.4062H12.5938C12.0587 18.4062 11.625 18.84 11.625 19.375V27.125H4.84375Z" fill="#497671" />
@@ -149,7 +149,7 @@
</svg> </svg>
</a> </a>
<a routerLink="/about" routerLinkActive="dexar-mobile-active" (click)="closeMenu()" class="dexar-mobile-item"> <a [routerLink]="'/about' | langRoute" routerLinkActive="dexar-mobile-active" (click)="closeMenu()" class="dexar-mobile-item">
<svg width="24" height="24" viewBox="0 0 31 31" fill="none"> <svg width="24" height="24" viewBox="0 0 31 31" fill="none">
<path d="M27.125 1.9375C28.1951 1.9375 29.0625 2.80495 29.0625 3.875V27.125C29.0625 28.1951 28.1951 29.0625 27.125 29.0625H3.875C2.80495 29.0625 1.9375 28.1951 1.9375 27.125V3.875C1.9375 2.80495 2.80495 1.9375 3.875 1.9375H27.125ZM3.875 0C1.7349 0 0 1.7349 0 3.875V27.125C0 29.2651 1.7349 31 3.875 31H27.125C29.2651 31 31 29.2651 31 27.125V3.875C31 1.7349 29.2651 0 27.125 0H3.875Z" fill="#497671" /> <path d="M27.125 1.9375C28.1951 1.9375 29.0625 2.80495 29.0625 3.875V27.125C29.0625 28.1951 28.1951 29.0625 27.125 29.0625H3.875C2.80495 29.0625 1.9375 28.1951 1.9375 27.125V3.875C1.9375 2.80495 2.80495 1.9375 3.875 1.9375H27.125ZM3.875 0C1.7349 0 0 1.7349 0 3.875V27.125C0 29.2651 1.7349 31 3.875 31H27.125C29.2651 31 31 29.2651 31 27.125V3.875C31 1.7349 29.2651 0 27.125 0H3.875Z" fill="#497671" />
<path d="M17.3032 12.764L12.8644 13.3203L12.7055 14.0582L13.5796 14.2172C14.1472 14.3534 14.2608 14.5577 14.1359 15.1254L12.7055 21.8461C12.3308 23.583 12.9098 24.4004 14.2721 24.4004C15.3279 24.4004 16.554 23.9122 17.1102 23.2424L17.2805 22.4364C16.8945 22.777 16.3269 22.9132 15.9523 22.9132C15.4187 22.9132 15.2257 22.5386 15.362 21.8801L17.3032 12.764Z" fill="#497671" /> <path d="M17.3032 12.764L12.8644 13.3203L12.7055 14.0582L13.5796 14.2172C14.1472 14.3534 14.2608 14.5577 14.1359 15.1254L12.7055 21.8461C12.3308 23.583 12.9098 24.4004 14.2721 24.4004C15.3279 24.4004 16.554 23.9122 17.1102 23.2424L17.2805 22.4364C16.8945 22.777 16.3269 22.9132 15.9523 22.9132C15.4187 22.9132 15.2257 22.5386 15.362 21.8801L17.3032 12.764Z" fill="#497671" />
@@ -161,7 +161,7 @@
</svg> </svg>
</a> </a>
<a routerLink="/contacts" routerLinkActive="dexar-mobile-active" (click)="closeMenu()" class="dexar-mobile-item"> <a [routerLink]="'/contacts' | langRoute" routerLinkActive="dexar-mobile-active" (click)="closeMenu()" class="dexar-mobile-item">
<svg width="24" height="24" viewBox="0 0 31 31" fill="none"> <svg width="24" height="24" viewBox="0 0 31 31" fill="none">
<path d="M0 7.75C0 5.6099 1.7349 3.875 3.875 3.875H27.125C29.2651 3.875 31 5.6099 31 7.75V23.25C31 25.3901 29.2651 27.125 27.125 27.125H3.875C1.7349 27.125 0 25.3901 0 23.25V7.75ZM3.875 5.8125C2.80495 5.8125 1.9375 6.67995 1.9375 7.75V8.17025L15.5 16.3078L29.0625 8.17025V7.75C29.0625 6.67995 28.1951 5.8125 27.125 5.8125H3.875ZM29.0625 10.4297L19.9406 15.9029L29.0625 21.5164V10.4297ZM28.9971 23.7511L18.0688 17.026L15.5 18.5672L12.9312 17.026L2.00292 23.7511C2.22375 24.5782 2.97823 25.1875 3.875 25.1875H27.125C28.0218 25.1875 28.7762 24.5782 28.9971 23.7511ZM1.9375 21.5164L11.0594 15.9029L1.9375 10.4297V21.5164Z" fill="#497671" /> <path d="M0 7.75C0 5.6099 1.7349 3.875 3.875 3.875H27.125C29.2651 3.875 31 5.6099 31 7.75V23.25C31 25.3901 29.2651 27.125 27.125 27.125H3.875C1.7349 27.125 0 25.3901 0 23.25V7.75ZM3.875 5.8125C2.80495 5.8125 1.9375 6.67995 1.9375 7.75V8.17025L15.5 16.3078L29.0625 8.17025V7.75C29.0625 6.67995 28.1951 5.8125 27.125 5.8125H3.875ZM29.0625 10.4297L19.9406 15.9029L29.0625 21.5164V10.4297ZM28.9971 23.7511L18.0688 17.026L15.5 18.5672L12.9312 17.026L2.00292 23.7511C2.22375 24.5782 2.97823 25.1875 3.875 25.1875H27.125C28.0218 25.1875 28.7762 24.5782 28.9971 23.7511ZM1.9375 21.5164L11.0594 15.9029L1.9375 10.4297V21.5164Z" fill="#497671" />
</svg> </svg>

View File

@@ -1,13 +1,14 @@
import { Component, ChangeDetectionStrategy, Renderer2, inject, DOCUMENT } from '@angular/core'; import { Component, ChangeDetectionStrategy, Renderer2, inject, DOCUMENT } from '@angular/core';
import { Router, RouterLink, RouterLinkActive } from '@angular/router'; import { Router, RouterLink, RouterLinkActive } from '@angular/router';
import { CartService } from '../../services'; import { CartService, LanguageService } from '../../services';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { LogoComponent } from '../logo/logo.component'; import { LogoComponent } from '../logo/logo.component';
import { LanguageSelectorComponent } from '../language-selector/language-selector.component'; import { LanguageSelectorComponent } from '../language-selector/language-selector.component';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-header', selector: 'app-header',
imports: [RouterLink, RouterLinkActive, LogoComponent, LanguageSelectorComponent], imports: [RouterLink, RouterLinkActive, LogoComponent, LanguageSelectorComponent, LangRoutePipe],
templateUrl: './header.component.html', templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'], styleUrls: ['./header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
@@ -21,6 +22,7 @@ export class HeaderComponent {
private renderer = inject(Renderer2); private renderer = inject(Renderer2);
private document = inject(DOCUMENT); private document = inject(DOCUMENT);
private langService = inject(LanguageService);
constructor(private cartService: CartService, private router: Router) { constructor(private cartService: CartService, private router: Router) {
this.cartItemCount = this.cartService.itemCount; this.cartItemCount = this.cartService.itemCount;
@@ -41,12 +43,14 @@ export class HeaderComponent {
} }
navigateToSearch(): void { navigateToSearch(): void {
this.router.navigate(['/search']); const lang = this.langService.currentLanguage();
this.router.navigate([`/${lang}/search`]);
} }
navigateToCatalog(): void { navigateToCatalog(): void {
this.closeMenu(); this.closeMenu();
this.router.navigate(['/']).then(() => { const lang = this.langService.currentLanguage();
this.router.navigate([`/${lang}`]).then(() => {
setTimeout(() => { setTimeout(() => {
this.document.getElementById('catalog')?.scrollIntoView({ behavior: 'smooth' }); this.document.getElementById('catalog')?.scrollIntoView({ behavior: 'smooth' });
}, 100); }, 100);

View File

@@ -16,7 +16,7 @@
[showIndicators]="true"> [showIndicators]="true">
<ng-template let-product pTemplate="item"> <ng-template let-product pTemplate="item">
<div class="item-card"> <div class="item-card">
<a [routerLink]="['/item', product.itemID]" class="item-link"> <a [routerLink]="['/item', product.itemID] | langRoute" class="item-link">
<div class="item-image"> <div class="item-image">
<img [src]="getItemImage(product)" [alt]="product.name" loading="lazy" /> <img [src]="getItemImage(product)" [alt]="product.name" loading="lazy" />
@if (product.discount > 0) { @if (product.discount > 0) {

View File

@@ -8,11 +8,12 @@ import { ApiService, CartService } from '../../services';
import { Item } from '../../models'; import { Item } from '../../models';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { getDiscountedPrice, getMainImage } from '../../utils/item.utils'; import { getDiscountedPrice, getMainImage } from '../../utils/item.utils';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-items-carousel', selector: 'app-items-carousel',
templateUrl: './items-carousel.component.html', templateUrl: './items-carousel.component.html',
imports: [DecimalPipe, RouterLink, CarouselModule, ButtonModule, TagModule], imports: [DecimalPipe, RouterLink, CarouselModule, ButtonModule, TagModule, LangRoutePipe],
styleUrls: ['./items-carousel.component.scss'], styleUrls: ['./items-carousel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })

View File

@@ -22,7 +22,7 @@ export class LanguageSelectorComponent {
selectLanguage(lang: Language): void { selectLanguage(lang: Language): void {
if (lang.enabled) { if (lang.enabled) {
this.languageService.setLanguage(lang.code); this.languageService.switchLanguage(lang.code);
this.dropdownOpen = false; this.dropdownOpen = false;
} }
} }

View File

@@ -0,0 +1,28 @@
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { LanguageService } from '../services/language.service';
export const languageGuard: CanActivateFn = (route, state) => {
const langService = inject(LanguageService);
const router = inject(Router);
const lang = route.params['lang'];
const langObj = langService.languages.find(l => l.code === lang);
if (langObj?.enabled) {
// Valid and enabled language — set it and proceed
langService.setLanguage(lang);
return true;
}
const defaultLang = langService.currentLanguage();
if (langObj && !langObj.enabled) {
// Known but disabled language — redirect to default lang, keep the rest of the path
const pathAfterLang = state.url.slice(1 + lang.length);
return router.createUrlTree([`/${defaultLang}${pathAfterLang}`]);
}
// Not a recognized language code — treat as a legacy URL without lang prefix
return router.createUrlTree([`/${defaultLang}${state.url}`]);
};

View File

@@ -18,7 +18,7 @@
</div> </div>
<h2>Корзина пуста</h2> <h2>Корзина пуста</h2>
<p>Добавьте товары, чтобы начать покупки</p> <p>Добавьте товары, чтобы начать покупки</p>
<a routerLink="/" class="shop-btn">Перейти к покупкам</a> <a [routerLink]="'/' | langRoute" class="shop-btn">Перейти к покупкам</a>
</div> </div>
} }
@@ -30,13 +30,13 @@
[class.swiped]="swipedItemId() === item.itemID" [class.swiped]="swipedItemId() === item.itemID"
(touchstart)="onSwipeStart(item.itemID, $event)"> (touchstart)="onSwipeStart(item.itemID, $event)">
<div class="cart-item"> <div class="cart-item">
<a [routerLink]="['/item', item.itemID]" class="item-image"> <a [routerLink]="['/item', item.itemID] | langRoute" class="item-image">
<img [src]="getMainImage(item)" [alt]="item.name" loading="lazy" /> <img [src]="getMainImage(item)" [alt]="item.name" loading="lazy" />
</a> </a>
<div class="item-info"> <div class="item-info">
<div class="item-header"> <div class="item-header">
<a [routerLink]="['/item', item.itemID]" class="item-name">{{ item.name }}</a> <a [routerLink]="['/item', item.itemID] | langRoute" class="item-name">{{ item.name }}</a>
<button class="remove-btn" (click)="removeItem(item.itemID)" title="Remove"> <button class="remove-btn" (click)="removeItem(item.itemID)" title="Remove">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6L6 18M6 6l12 12"/> <path d="M18 6L6 18M6 6l12 12"/>
@@ -114,10 +114,10 @@
<span class="checkmark"></span> <span class="checkmark"></span>
<span class="terms-text"> <span class="terms-text">
Я согласен с Я согласен с
<a routerLink="/public-offer" target="_blank">публичной офертой</a>, <a [routerLink]="'/public-offer' | langRoute" target="_blank">публичной офертой</a>,
<a routerLink="/return-policy" target="_blank">политикой возврата</a>, <a [routerLink]="'/return-policy' | langRoute" target="_blank">политикой возврата</a>,
<a routerLink="/guarantee" target="_blank">условиями гарантии</a> и <a [routerLink]="'/guarantee' | langRoute" target="_blank">условиями гарантии</a> и
<a routerLink="/privacy-policy" target="_blank">политикой конфиденциальности</a> <a [routerLink]="'/privacy-policy' | langRoute" target="_blank">политикой конфиденциальности</a>
</span> </span>
</label> </label>
</div> </div>

View File

@@ -2,17 +2,18 @@ import { Component, computed, ChangeDetectionStrategy, signal, OnDestroy } from
import { DecimalPipe } from '@angular/common'; import { DecimalPipe } from '@angular/common';
import { Router, RouterLink } from '@angular/router'; import { Router, RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { CartService, ApiService } from '../../services'; import { CartService, ApiService, LanguageService } from '../../services';
import { Item, CartItem } from '../../models'; import { Item, CartItem } from '../../models';
import { interval, Subscription } from 'rxjs'; import { interval, Subscription } from 'rxjs';
import { switchMap, take } from 'rxjs/operators'; import { switchMap, take } from 'rxjs/operators';
import { EmptyCartIconComponent } from '../../components/empty-cart-icon/empty-cart-icon.component'; import { EmptyCartIconComponent } from '../../components/empty-cart-icon/empty-cart-icon.component';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { getDiscountedPrice, getMainImage, trackByItemId } from '../../utils/item.utils'; import { getDiscountedPrice, getMainImage, trackByItemId } from '../../utils/item.utils';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-cart', selector: 'app-cart',
imports: [DecimalPipe, RouterLink, FormsModule, EmptyCartIconComponent], imports: [DecimalPipe, RouterLink, FormsModule, EmptyCartIconComponent, LangRoutePipe],
templateUrl: './cart.component.html', templateUrl: './cart.component.html',
styleUrls: ['./cart.component.scss'], styleUrls: ['./cart.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
@@ -52,7 +53,8 @@ export class CartComponent implements OnDestroy {
constructor( constructor(
private cartService: CartService, private cartService: CartService,
private apiService: ApiService, private apiService: ApiService,
private router: Router private router: Router,
private langService: LanguageService
) { ) {
this.items = this.cartService.items; this.items = this.cartService.items;
this.itemCount = this.cartService.itemCount; this.itemCount = this.cartService.itemCount;
@@ -316,7 +318,8 @@ export class CartComponent implements OnDestroy {
// Close popup and redirect to home page // Close popup and redirect to home page
setTimeout(() => { setTimeout(() => {
this.closePaymentPopup(); this.closePaymentPopup();
this.router.navigate(['/']); const lang = this.langService.currentLanguage();
this.router.navigate([`/${lang}`]);
}, 500); }, 500);
}, },
error: (err) => { error: (err) => {

View File

@@ -10,7 +10,7 @@
<div class="items-grid"> <div class="items-grid">
@for (item of items(); track trackByItemId($index, item)) { @for (item of items(); track trackByItemId($index, item)) {
<div class="item-card"> <div class="item-card">
<a [routerLink]="['/item', item.itemID]" class="item-link"> <a [routerLink]="['/item', item.itemID] | langRoute" class="item-link">
<div class="item-image"> <div class="item-image">
<img [src]="getMainImage(item)" [alt]="item.name" loading="lazy" decoding="async" width="300" height="300" /> <img [src]="getMainImage(item)" [alt]="item.name" loading="lazy" decoding="async" width="300" height="300" />
@if (item.discount > 0) { @if (item.discount > 0) {
@@ -77,7 +77,7 @@
</div> </div>
<h3>Упс! Здесь пока пусто</h3> <h3>Упс! Здесь пока пусто</h3>
<p>В этой категории ещё нет товаров, но скоро они появятся</p> <p>В этой категории ещё нет товаров, но скоро они появятся</p>
<a routerLink="/" class="no-items-btn">На главную</a> <a [routerLink]="'/' | langRoute" class="no-items-btn">На главную</a>
</div> </div>
} }

View File

@@ -5,10 +5,11 @@ import { ApiService, CartService } from '../../services';
import { Item } from '../../models'; import { Item } from '../../models';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { getDiscountedPrice, getMainImage, trackByItemId } from '../../utils/item.utils'; import { getDiscountedPrice, getMainImage, trackByItemId } from '../../utils/item.utils';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-category', selector: 'app-category',
imports: [DecimalPipe, RouterLink], imports: [DecimalPipe, RouterLink, LangRoutePipe],
templateUrl: './category.component.html', templateUrl: './category.component.html',
styleUrls: ['./category.component.scss'], styleUrls: ['./category.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -21,7 +21,7 @@
@if (subcategories().length > 0) { @if (subcategories().length > 0) {
<div class="categories-grid"> <div class="categories-grid">
@for (cat of subcategories(); track trackByCategoryId($index, cat)) { @for (cat of subcategories(); track trackByCategoryId($index, cat)) {
<a [routerLink]="['/category', cat.categoryID]" class="category-card"> <a [routerLink]="['/category', cat.categoryID] | langRoute" class="category-card">
<div class="category-image"> <div class="category-image">
@if (cat.icon) { @if (cat.icon) {
<img [src]="cat.icon" [alt]="cat.name" loading="lazy" decoding="async" /> <img [src]="cat.icon" [alt]="cat.name" loading="lazy" decoding="async" />
@@ -47,7 +47,7 @@
</div> </div>
<h3>Упс! Подкатегорий пока нет</h3> <h3>Упс! Подкатегорий пока нет</h3>
<p>В этом разделе ещё нет подкатегорий, но скоро они появятся</p> <p>В этом разделе ещё нет подкатегорий, но скоро они появятся</p>
<a routerLink="/" class="no-subcats-btn">На главную</a> <a [routerLink]="'/' | langRoute" class="no-subcats-btn">На главную</a>
</div> </div>
} }
} }

View File

@@ -1,12 +1,13 @@
import { Component, OnInit, OnDestroy, signal, ChangeDetectionStrategy } from '@angular/core'; import { Component, OnInit, OnDestroy, signal, ChangeDetectionStrategy } from '@angular/core';
import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ApiService } from '../../services'; import { ApiService, LanguageService } from '../../services';
import { Category } from '../../models'; import { Category } from '../../models';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-subcategories', selector: 'app-subcategories',
imports: [RouterLink], imports: [RouterLink, LangRoutePipe],
templateUrl: './subcategories.component.html', templateUrl: './subcategories.component.html',
styleUrls: ['./subcategories.component.scss'], styleUrls: ['./subcategories.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
@@ -23,7 +24,8 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private apiService: ApiService private apiService: ApiService,
private langService: LanguageService
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
@@ -48,7 +50,8 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
if (!subs || subs.length === 0) { if (!subs || subs.length === 0) {
// No subcategories: redirect to items list for this category // No subcategories: redirect to items list for this category
this.router.navigate(['/category', parentID, 'items'], { replaceUrl: true }); const lang = this.langService.currentLanguage();
this.router.navigate([`/${lang}/category`, parentID, 'items'], { replaceUrl: true });
} else { } else {
this.subcategories.set(subs); this.subcategories.set(subs);
} }

View File

@@ -5,7 +5,7 @@
<div class="novo-hero-content"> <div class="novo-hero-content">
<h1 class="novo-hero-title">Добро пожаловать в {{ brandName }}</h1> <h1 class="novo-hero-title">Добро пожаловать в {{ brandName }}</h1>
<p class="novo-hero-subtitle">Найдите всё, что нужно, в одном месте</p> <p class="novo-hero-subtitle">Найдите всё, что нужно, в одном месте</p>
<a routerLink="/search" class="novo-hero-btn"> <a [routerLink]="'/search' | langRoute" class="novo-hero-btn">
Начать поиск Начать поиск
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="5" y1="12" x2="19" y2="12"></line> <line x1="5" y1="12" x2="19" y2="12"></line>
@@ -50,7 +50,7 @@
} @else { } @else {
<div class="novo-categories-grid"> <div class="novo-categories-grid">
@for (category of topLevelCategories(); track category.categoryID) { @for (category of topLevelCategories(); track category.categoryID) {
<a [routerLink]="['/category', category.categoryID]" class="novo-category-card"> <a [routerLink]="['/category', category.categoryID] | langRoute" class="novo-category-card">
<div class="novo-category-image"> <div class="novo-category-image">
@if (category.icon) { @if (category.icon) {
<img [src]="category.icon" [alt]="category.name" loading="lazy" /> <img [src]="category.icon" [alt]="category.name" loading="lazy" />
@@ -126,7 +126,7 @@
} @else { } @else {
<div class="dexar-categories-grid"> <div class="dexar-categories-grid">
@for (category of topLevelCategories(); track category.categoryID) { @for (category of topLevelCategories(); track category.categoryID) {
<a [routerLink]="['/category', category.categoryID]" <a [routerLink]="['/category', category.categoryID] | langRoute"
class="dexar-category-card" class="dexar-category-card"
[class.dexar-category-card--wide]="isWideCategory(category.categoryID)"> [class.dexar-category-card--wide]="isWideCategory(category.categoryID)">
<div class="dexar-category-image"> <div class="dexar-category-image">

View File

@@ -1,13 +1,14 @@
import { Component, OnInit, signal, computed, ChangeDetectionStrategy } from '@angular/core'; import { Component, OnInit, signal, computed, ChangeDetectionStrategy } from '@angular/core';
import { Router, RouterLink } from '@angular/router'; import { Router, RouterLink } from '@angular/router';
import { ApiService } from '../../services'; import { ApiService, LanguageService } from '../../services';
import { Category } from '../../models'; import { Category } from '../../models';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { ItemsCarouselComponent } from '../../components/items-carousel/items-carousel.component'; import { ItemsCarouselComponent } from '../../components/items-carousel/items-carousel.component';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
imports: [RouterLink, ItemsCarouselComponent], imports: [RouterLink, ItemsCarouselComponent, LangRoutePipe],
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'], styleUrls: ['./home.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
@@ -48,7 +49,7 @@ export class HomeComponent implements OnInit {
return cache; return cache;
}); });
constructor(private apiService: ApiService, private router: Router) {} constructor(private apiService: ApiService, private router: Router, private langService: LanguageService) {}
ngOnInit(): void { ngOnInit(): void {
this.loadCategories(); this.loadCategories();
@@ -103,7 +104,8 @@ export class HomeComponent implements OnInit {
} }
navigateToSearch(): void { navigateToSearch(): void {
this.router.navigate(['/search']); const lang = this.langService.currentLanguage();
this.router.navigate([`/${lang}/search`]);
} }
scrollToCatalog(): void { scrollToCatalog(): void {

View File

@@ -232,7 +232,7 @@
<div class="faq-item"> <div class="faq-item">
<h3>Какие товары вернуть нельзя?</h3> <h3>Какие товары вернуть нельзя?</h3>
<p>Нельзя вернуть товары, перечисленные в Постановлении Правительства РФ №2463: медикаменты, косметику, бельё, активированные цифровые продукты и индивидуальные заказы. Подробности смотрите в разделе <a routerLink="/return-policy">«Политика возврата»</a>.</p> <p>Нельзя вернуть товары, перечисленные в Постановлении Правительства РФ №2463: медикаменты, косметику, бельё, активированные цифровые продукты и индивидуальные заказы. Подробности смотрите в разделе <a [routerLink]="'/return-policy' | langRoute">«Политика возврата»</a>.</p>
</div> </div>
<div class="faq-item"> <div class="faq-item">
@@ -274,7 +274,7 @@
<div class="faq-item"> <div class="faq-item">
<h3>Как вы защищаете мои личные данные?</h3> <h3>Как вы защищаете мои личные данные?</h3>
<p>Используем современные методы шифрования SSL/TLS, не храним реквизиты карт, выполняем требования закона №152-ФЗ о защите персональной информации. Подробности — в <a routerLink="/privacy-policy">Политике конфиденциальности</a>.</p> <p>Используем современные методы шифрования SSL/TLS, не храним реквизиты карт, выполняем требования закона №152-ФЗ о защите персональной информации. Подробности — в <a [routerLink]="'/privacy-policy' | langRoute">Политике конфиденциальности</a>.</p>
</div> </div>
<div class="faq-item"> <div class="faq-item">

View File

@@ -1,9 +1,11 @@
import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterLink } from '@angular/router';
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../environments/environment';
import { LangRoutePipe } from '../../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-faq', selector: 'app-faq',
imports: [], imports: [RouterLink, LangRoutePipe],
templateUrl: './faq.component.html', templateUrl: './faq.component.html',
styleUrls: ['./faq.component.scss'], styleUrls: ['./faq.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -11,7 +11,7 @@
@if (error()) { @if (error()) {
<div class="novo-error"> <div class="novo-error">
<p>{{ error() }}</p> <p>{{ error() }}</p>
<a routerLink="/" class="novo-back-link">Вернуться</a> <a [routerLink]="'/' | langRoute" class="novo-back-link">Вернуться</a>
</div> </div>
} }
@@ -204,7 +204,7 @@
@if (error()) { @if (error()) {
<div class="dx-error"> <div class="dx-error">
<p>{{ error() }}</p> <p>{{ error() }}</p>
<a routerLink="/" class="dx-back-link">Вернуться на главную</a> <a [routerLink]="'/' | langRoute" class="dx-back-link">Вернуться на главную</a>
</div> </div>
} }

View File

@@ -9,10 +9,11 @@ import { Subscription } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { SecurityContext } from '@angular/core'; import { SecurityContext } from '@angular/core';
import { getDiscountedPrice } from '../../utils/item.utils'; import { getDiscountedPrice } from '../../utils/item.utils';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-item-detail', selector: 'app-item-detail',
imports: [DecimalPipe, RouterLink, FormsModule], imports: [DecimalPipe, RouterLink, FormsModule, LangRoutePipe],
templateUrl: './item-detail.component.html', templateUrl: './item-detail.component.html',
styleUrls: ['./item-detail.component.scss'], styleUrls: ['./item-detail.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -105,7 +105,7 @@
<section class="info-card wide"> <section class="info-card wide">
<div class="card-icon">↩️</div> <div class="card-icon">↩️</div>
<h2>6. Возврат средств</h2> <h2>6. Возврат средств</h2>
<p>6.1. Порядок возврата денежных средств регулируется <a routerLink="/return-policy">Политикой возврата</a> и зависит от типа приобретенного Товара/Услуги.</p> <p>6.1. Порядок возврата денежных средств регулируется <a [routerLink]="'/return-policy' | langRoute">Политикой возврата</a> и зависит от типа приобретенного Товара/Услуги.</p>
<p>6.2. Возврат средств производится на тот же платежный инструмент, с которого была произведена оплата.</p> <p>6.2. Возврат средств производится на тот же платежный инструмент, с которого была произведена оплата.</p>
<p>6.3. Срок возврата денежных средств составляет:</p> <p>6.3. Срок возврата денежных средств составляет:</p>
<div class="refund-times"> <div class="refund-times">
@@ -232,7 +232,7 @@
<section class="legal-section"> <section class="legal-section">
<h2>6. Возврат средств</h2> <h2>6. Возврат средств</h2>
<p>6.1. Порядок возврата денежных средств регулируется <a routerLink="/return-policy">Политикой возврата</a> и зависит от типа приобретенного Товара/Услуги.</p> <p>6.1. Порядок возврата денежных средств регулируется <a [routerLink]="'/return-policy' | langRoute">Политикой возврата</a> и зависит от типа приобретенного Товара/Услуги.</p>
<p>6.2. Возврат средств производится на тот же платежный инструмент, с которого была произведена оплата.</p> <p>6.2. Возврат средств производится на тот же платежный инструмент, с которого была произведена оплата.</p>
<p>6.3. Срок возврата денежных средств составляет:</p> <p>6.3. Срок возврата денежных средств составляет:</p>
<ul> <ul>

View File

@@ -1,10 +1,11 @@
import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../environments/environment';
import { LangRoutePipe } from '../../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-payment-terms', selector: 'app-payment-terms',
imports: [RouterLink], imports: [RouterLink, LangRoutePipe],
templateUrl: './payment-terms.component.html', templateUrl: './payment-terms.component.html',
styleUrls: ['./payment-terms.component.scss'], styleUrls: ['./payment-terms.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -33,7 +33,7 @@
<p>1.4. Акцепт происходит автоматически при любом действии: визите, регистрации, оформлении покупки.</p> <p>1.4. Акцепт происходит автоматически при любом действии: визите, регистрации, оформлении покупки.</p>
<p>1.5. Подписание бумажного договора не требуется — электронная форма юридически действительна.</p> <p>1.5. Подписание бумажного договора не требуется — электронная форма юридически действительна.</p>
<p>1.6. Несогласие с условиями означает обязанность покинуть сайт.</p> <p>1.6. Несогласие с условиями означает обязанность покинуть сайт.</p>
<p>1.7. Также применяется <a routerLink="/privacy-policy">Политика конфиденциальности</a>.</p> <p>1.7. Также применяется <a [routerLink]="'/privacy-policy' | langRoute">Политика конфиденциальности</a>.</p>
<p>1.8. Мы можем обновлять условия в одностороннем порядке.</p> <p>1.8. Мы можем обновлять условия в одностороннем порядке.</p>
<p>1.9. Промо-кампании могут иметь специальные правила.</p> <p>1.9. Промо-кампании могут иметь специальные правила.</p>
</section> </section>
@@ -206,7 +206,7 @@
<section class="info-card wide"> <section class="info-card wide">
<div class="card-icon">↩️</div> <div class="card-icon">↩️</div>
<h2>13. Возврат и обмен товара</h2> <h2>13. Возврат и обмен товара</h2>
<p><strong>13.1. Общие правила:</strong> Цифровые товары не подлежат возврату. Физические товары — согласно <a routerLink="/return-policy">Политике возврата</a> и законам о правах потребителей.</p> <p><strong>13.1. Общие правила:</strong> Цифровые товары не подлежат возврату. Физические товары — согласно <a [routerLink]="'/return-policy' | langRoute">Политике возврата</a> и законам о правах потребителей.</p>
<p><strong>13.2. Процедура возврата:</strong> В соответствии с соглашением и законодательством РФ.</p> <p><strong>13.2. Процедура возврата:</strong> В соответствии с соглашением и законодательством РФ.</p>
<p><strong>13.3. Акционные наборы:</strong> Возврат только в комплексе, отдельные товары вернуть нельзя.</p> <p><strong>13.3. Акционные наборы:</strong> Возврат только в комплексе, отдельные товары вернуть нельзя.</p>
<p><strong>13.4. Затраты на доставку:</strong> При возврате качественного товара продавец может взыскать затраты на доставку.</p> <p><strong>13.4. Затраты на доставку:</strong> При возврате качественного товара продавец может взыскать затраты на доставку.</p>
@@ -316,7 +316,7 @@
<p>1.7. В случае несогласия с условиями Пользователь обязуется незамедлительно прекратить пользование ресурсом.</p> <p>1.7. В случае несогласия с условиями Пользователь обязуется незамедлительно прекратить пользование ресурсом.</p>
<p>1.8. Дополнительно регулирование использования сайта осуществляется <a routerLink="/privacy-policy">Политикой обработки персональных данных</a>.</p> <p>1.8. Дополнительно регулирование использования сайта осуществляется <a [routerLink]="'/privacy-policy' | langRoute">Политикой обработки персональных данных</a>.</p>
<p>1.9. Изменения в соглашение могут вноситься Владельцем без предварительного уведомления и становятся обязательными с момента публикации изменений.</p> <p>1.9. Изменения в соглашение могут вноситься Владельцем без предварительного уведомления и становятся обязательными с момента публикации изменений.</p>
@@ -640,7 +640,7 @@
<h2>13. Возврат и обмен товара</h2> <h2>13. Возврат и обмен товара</h2>
<p><strong>13.1. Общие правила возврата</strong></p> <p><strong>13.1. Общие правила возврата</strong></p>
<p>Цифровые товары (предоставляемые в электронной форме) не подлежат возврату согласно российскому законодательству. Возврат физических товаров осуществляется в соответствии с разделом <a routerLink="/return-policy">«Политика возврата»</a> и действующими законами о правах потребителей.</p> <p>Цифровые товары (предоставляемые в электронной форме) не подлежат возврату согласно российскому законодательству. Возврат физических товаров осуществляется в соответствии с разделом <a [routerLink]="'/return-policy' | langRoute">«Политика возврата»</a> и действующими законами о правах потребителей.</p>
<p><strong>13.2. Возврат товара</strong></p> <p><strong>13.2. Возврат товара</strong></p>
<p>Возврат или замена товаров, представленных на сайте и подлежащих возврату, происходят в соответствии с данным соглашением и законодательством Российской Федерации.</p> <p>Возврат или замена товаров, представленных на сайте и подлежащих возврату, происходят в соответствии с данным соглашением и законодательством Российской Федерации.</p>

View File

@@ -1,10 +1,11 @@
import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../environments/environment';
import { LangRoutePipe } from '../../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-public-offer', selector: 'app-public-offer',
imports: [RouterLink], imports: [RouterLink, LangRoutePipe],
templateUrl: './public-offer.component.html', templateUrl: './public-offer.component.html',
styleUrls: ['./public-offer.component.scss'], styleUrls: ['./public-offer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -57,7 +57,7 @@
<div class="items-grid"> <div class="items-grid">
@for (item of items(); track trackByItemId($index, item)) { @for (item of items(); track trackByItemId($index, item)) {
<div class="item-card"> <div class="item-card">
<a [routerLink]="['/item', item.itemID]" class="item-link"> <a [routerLink]="['/item', item.itemID] | langRoute" class="item-link">
<div class="item-image"> <div class="item-image">
<img [src]="getMainImage(item)" [alt]="item.name" loading="lazy" decoding="async" width="300" height="300" /> <img [src]="getMainImage(item)" [alt]="item.name" loading="lazy" decoding="async" width="300" height="300" />
@if (item.discount > 0) { @if (item.discount > 0) {

View File

@@ -7,10 +7,11 @@ import { Item } from '../../models';
import { Subject, Subscription } from 'rxjs'; import { Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { getDiscountedPrice, getMainImage, trackByItemId } from '../../utils/item.utils'; import { getDiscountedPrice, getMainImage, trackByItemId } from '../../utils/item.utils';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
@Component({ @Component({
selector: 'app-search', selector: 'app-search',
imports: [DecimalPipe, FormsModule, RouterLink], imports: [DecimalPipe, FormsModule, RouterLink, LangRoutePipe],
templateUrl: './search.component.html', templateUrl: './search.component.html',
styleUrls: ['./search.component.scss'], styleUrls: ['./search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -0,0 +1,25 @@
import { Pipe, PipeTransform, inject } from '@angular/core';
import { LanguageService } from '../services/language.service';
@Pipe({
name: 'langRoute',
pure: false
})
export class LangRoutePipe implements PipeTransform {
private langService = inject(LanguageService);
transform(value: string | (string | number)[]): string | (string | number)[] {
const lang = this.langService.currentLanguage();
if (typeof value === 'string') {
return value === '/' ? `/${lang}` : `/${lang}${value}`;
}
if (Array.isArray(value) && value.length > 0) {
const [first, ...rest] = value;
return [`/${lang}${first}`, ...rest];
}
return value;
}
}

View File

@@ -1,4 +1,5 @@
import { Injectable, signal } from '@angular/core'; import { Injectable, signal } from '@angular/core';
import { Router } from '@angular/router';
export interface Language { export interface Language {
code: string; code: string;
@@ -22,7 +23,7 @@ export class LanguageService {
currentLanguage = this.currentLanguageSignal.asReadonly(); currentLanguage = this.currentLanguageSignal.asReadonly();
constructor() { constructor(private router: Router) {
// Load saved language from localStorage // Load saved language from localStorage
const savedLang = localStorage.getItem('selectedLanguage'); const savedLang = localStorage.getItem('selectedLanguage');
if (savedLang && this.languages.find(l => l.code === savedLang && l.enabled)) { if (savedLang && this.languages.find(l => l.code === savedLang && l.enabled)) {
@@ -38,6 +39,19 @@ export class LanguageService {
} }
} }
/** Change language and navigate to the same page with the new prefix */
switchLanguage(langCode: string): void {
const lang = this.languages.find(l => l.code === langCode);
if (!lang?.enabled) return;
const currentUrl = this.router.url;
const currentLang = this.currentLanguageSignal();
const newUrl = currentUrl.replace(new RegExp(`^/${currentLang}`), `/${langCode}`);
this.setLanguage(langCode);
this.router.navigateByUrl(newUrl);
}
getCurrentLanguage(): Language | undefined { getCurrentLanguage(): Language | undefined {
return this.languages.find(l => l.code === this.currentLanguageSignal()); return this.languages.find(l => l.code === this.currentLanguageSignal());
} }