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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,13 @@
import { Component, OnInit, OnDestroy, signal, ChangeDetectionStrategy } from '@angular/core';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ApiService } from '../../services';
import { ApiService, LanguageService } from '../../services';
import { Category } from '../../models';
import { Subscription } from 'rxjs';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
@Component({
selector: 'app-subcategories',
imports: [RouterLink],
imports: [RouterLink, LangRoutePipe],
templateUrl: './subcategories.component.html',
styleUrls: ['./subcategories.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@@ -23,7 +24,8 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
constructor(
private route: ActivatedRoute,
private router: Router,
private apiService: ApiService
private apiService: ApiService,
private langService: LanguageService
) {}
ngOnInit(): void {
@@ -48,7 +50,8 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
if (!subs || subs.length === 0) {
// 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 {
this.subcategories.set(subs);
}

View File

@@ -5,7 +5,7 @@
<div class="novo-hero-content">
<h1 class="novo-hero-title">Добро пожаловать в {{ brandName }}</h1>
<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">
<line x1="5" y1="12" x2="19" y2="12"></line>
@@ -50,7 +50,7 @@
} @else {
<div class="novo-categories-grid">
@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">
@if (category.icon) {
<img [src]="category.icon" [alt]="category.name" loading="lazy" />
@@ -126,7 +126,7 @@
} @else {
<div class="dexar-categories-grid">
@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--wide]="isWideCategory(category.categoryID)">
<div class="dexar-category-image">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -105,7 +105,7 @@
<section class="info-card wide">
<div class="card-icon">↩️</div>
<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.3. Срок возврата денежных средств составляет:</p>
<div class="refund-times">
@@ -232,7 +232,7 @@
<section class="legal-section">
<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.3. Срок возврата денежных средств составляет:</p>
<ul>

View File

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

View File

@@ -33,7 +33,7 @@
<p>1.4. Акцепт происходит автоматически при любом действии: визите, регистрации, оформлении покупки.</p>
<p>1.5. Подписание бумажного договора не требуется — электронная форма юридически действительна.</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.9. Промо-кампании могут иметь специальные правила.</p>
</section>
@@ -206,7 +206,7 @@
<section class="info-card wide">
<div class="card-icon">↩️</div>
<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.3. Акционные наборы:</strong> Возврат только в комплексе, отдельные товары вернуть нельзя.</p>
<p><strong>13.4. Затраты на доставку:</strong> При возврате качественного товара продавец может взыскать затраты на доставку.</p>
@@ -316,7 +316,7 @@
<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>
@@ -640,7 +640,7 @@
<h2>13. Возврат и обмен товара</h2>
<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>Возврат или замена товаров, представленных на сайте и подлежащих возврату, происходят в соответствии с данным соглашением и законодательством Российской Федерации.</p>

View File

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

View File

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

View File

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