added translations
This commit is contained in:
@@ -1,14 +1,14 @@
|
|||||||
@if (checkingServer()) {
|
@if (checkingServer()) {
|
||||||
<div class="server-check-overlay">
|
<div class="server-check-overlay">
|
||||||
<div class="spinner-large"></div>
|
<div class="spinner-large"></div>
|
||||||
<p>Подключение к серверу...</p>
|
<p>{{ 'app.connecting' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
} @else if (!serverAvailable()) {
|
} @else if (!serverAvailable()) {
|
||||||
<div class="server-error-overlay">
|
<div class="server-error-overlay">
|
||||||
<div class="error-icon">⚠️</div>
|
<div class="error-icon">⚠️</div>
|
||||||
<h2>Сервер недоступен</h2>
|
<h2>{{ 'app.serverUnavailable' | translate }}</h2>
|
||||||
<p>Не удалось подключиться к серверу. Проверьте подключение к интернету.</p>
|
<p>{{ 'app.serverError' | translate }}</p>
|
||||||
<button class="retry-btn" (click)="retryConnection()">Повторить попытку</button>
|
<button class="retry-btn" (click)="retryConnection()">{{ 'app.retryConnection' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<app-header></app-header>
|
<app-header></app-header>
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import { filter, first } from 'rxjs/operators';
|
|||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { SwUpdate } from '@angular/service-worker';
|
import { SwUpdate } from '@angular/service-worker';
|
||||||
|
import { TranslatePipe } from './i18n/translate.pipe';
|
||||||
|
import { TranslateService } from './i18n/translate.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet, HeaderComponent, FooterComponent, BackButtonComponent],
|
imports: [RouterOutlet, HeaderComponent, FooterComponent, BackButtonComponent, TranslatePipe],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.scss'
|
styleUrl: './app.scss'
|
||||||
})
|
})
|
||||||
@@ -30,9 +32,10 @@ export class App implements OnInit {
|
|||||||
private swUpdate = inject(SwUpdate);
|
private swUpdate = inject(SwUpdate);
|
||||||
private appRef = inject(ApplicationRef);
|
private appRef = inject(ApplicationRef);
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
|
private i18n = inject(TranslateService);
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.titleService.setTitle(`${environment.brandFullName} - Маркетплейс товаров и услуг`);
|
this.titleService.setTitle(`${environment.brandFullName} - ${this.i18n.t('app.pageTitle')}`);
|
||||||
this.checkServerHealth();
|
this.checkServerHealth();
|
||||||
this.setupAutoUpdates();
|
this.setupAutoUpdates();
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { TranslateService } from '../../i18n/translate.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-back-button',
|
selector: 'app-back-button',
|
||||||
template: `
|
template: `
|
||||||
@if (!isnovo) {
|
@if (!isnovo) {
|
||||||
<button class="dexar-back-btn" (click)="goBack()" aria-label="Назад">
|
<button class="dexar-back-btn" (click)="goBack()" [attr.aria-label]="i18n.t('itemDetail.back')">
|
||||||
<svg width="37" height="24" viewBox="0 0 37 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="37" height="24" viewBox="0 0 37 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M4.73 11.46c-.97-.52-.97-1.4 0-1.92L20.39 1.21c1.48-.79 3.89-.19 3.89.95v3.74h6.94c1.28 0 2.31.59 2.31 1.31v6.56c0 .73-1.03 1.31-2.31 1.31h-6.94v3.74c0 1.15-2.42 1.74-3.89.96L4.73 11.46Z"
|
<path d="M4.73 11.46c-.97-.52-.97-1.4 0-1.92L20.39 1.21c1.48-.79 3.89-.19 3.89.95v3.74h6.94c1.28 0 2.31.59 2.31 1.31v6.56c0 .73-1.03 1.31-2.31 1.31h-6.94v3.74c0 1.15-2.42 1.74-3.89.96L4.73 11.46Z"
|
||||||
fill="#497671" fill-opacity="0.75" stroke="white" stroke-opacity="0.6" stroke-linejoin="round"/>
|
fill="#497671" fill-opacity="0.75" stroke="white" stroke-opacity="0.6" stroke-linejoin="round"/>
|
||||||
@@ -60,6 +61,7 @@ import { environment } from '../../../environments/environment';
|
|||||||
})
|
})
|
||||||
export class BackButtonComponent {
|
export class BackButtonComponent {
|
||||||
isnovo = environment.theme === 'novo';
|
isnovo = environment.theme === 'novo';
|
||||||
|
i18n = inject(TranslateService);
|
||||||
|
|
||||||
constructor(private location: Location) {}
|
constructor(private location: Location) {}
|
||||||
|
|
||||||
|
|||||||
@@ -5,33 +5,33 @@
|
|||||||
<div class="novo-footer-top">
|
<div class="novo-footer-top">
|
||||||
<div class="novo-footer-col">
|
<div class="novo-footer-col">
|
||||||
<h4>{{ brandName }}</h4>
|
<h4>{{ brandName }}</h4>
|
||||||
<p class="novo-footer-desc">Современный маркетплейс для комфортных покупок</p>
|
<p class="novo-footer-desc">{{ 'footer.description' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="novo-footer-col">
|
<div class="novo-footer-col">
|
||||||
<h4>Компания</h4>
|
<h4>{{ 'footer.company' | translate }}</h4>
|
||||||
<ul class="novo-footer-links">
|
<ul class="novo-footer-links">
|
||||||
<li><a [routerLink]="'/about' | langRoute">О нас</a></li>
|
<li><a [routerLink]="'/about' | langRoute">{{ 'footer.aboutUs' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/contacts' | langRoute">Контакты</a></li>
|
<li><a [routerLink]="'/contacts' | langRoute">{{ 'footer.contacts' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/company-details' | langRoute">Реквизиты</a></li>
|
<li><a [routerLink]="'/company-details' | langRoute">{{ 'footer.requisites' | translate }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="novo-footer-col">
|
<div class="novo-footer-col">
|
||||||
<h4>Поддержка</h4>
|
<h4>{{ 'footer.support' | translate }}</h4>
|
||||||
<ul class="novo-footer-links">
|
<ul class="novo-footer-links">
|
||||||
<li><a [routerLink]="'/faq' | langRoute">FAQ</a></li>
|
<li><a [routerLink]="'/faq' | langRoute">{{ 'footer.faq' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/delivery' | langRoute">Доставка</a></li>
|
<li><a [routerLink]="'/delivery' | langRoute">{{ 'footer.delivery' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/guarantee' | langRoute">Гарантия</a></li>
|
<li><a [routerLink]="'/guarantee' | langRoute">{{ 'footer.guarantee' | translate }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="novo-footer-col">
|
<div class="novo-footer-col">
|
||||||
<h4>Правовая информация</h4>
|
<h4>{{ 'footer.legal' | translate }}</h4>
|
||||||
<ul class="novo-footer-links">
|
<ul class="novo-footer-links">
|
||||||
<li><a [routerLink]="'/public-offer' | langRoute">Оферта</a></li>
|
<li><a [routerLink]="'/public-offer' | langRoute">{{ 'footer.offer' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/privacy-policy' | langRoute">Конфиденциальность</a></li>
|
<li><a [routerLink]="'/privacy-policy' | langRoute">{{ 'footer.privacy' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/return-policy' | langRoute">Возврат</a></li>
|
<li><a [routerLink]="'/return-policy' | langRoute">{{ 'footer.returns' | translate }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,35 +61,35 @@
|
|||||||
|
|
||||||
<div class="dexar-footer-columns">
|
<div class="dexar-footer-columns">
|
||||||
<div class="dexar-footer-col">
|
<div class="dexar-footer-col">
|
||||||
<h4>Информация</h4>
|
<h4>{{ 'footer.info' | translate }}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a [routerLink]="'/about' | langRoute">О компании</a></li>
|
<li><a [routerLink]="'/about' | langRoute">{{ 'footer.aboutCompany' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/contacts' | langRoute">Контакты</a></li>
|
<li><a [routerLink]="'/contacts' | langRoute">{{ 'footer.contacts' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/company-details' | langRoute">Реквизиты</a></li>
|
<li><a [routerLink]="'/company-details' | langRoute">{{ 'footer.requisites' | translate }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dexar-footer-col">
|
<div class="dexar-footer-col">
|
||||||
<h4>Документы</h4>
|
<h4>{{ 'footer.documents' | translate }}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a [routerLink]="'/payment-terms' | langRoute">Правила оплаты</a></li>
|
<li><a [routerLink]="'/payment-terms' | langRoute">{{ 'footer.paymentRules' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/return-policy' | langRoute">Политика возврата</a></li>
|
<li><a [routerLink]="'/return-policy' | langRoute">{{ 'footer.returnPolicy' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/public-offer' | langRoute">Публичная оферта</a></li>
|
<li><a [routerLink]="'/public-offer' | langRoute">{{ 'footer.publicOffer' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/privacy-policy' | langRoute">Конфиденциальность</a></li>
|
<li><a [routerLink]="'/privacy-policy' | langRoute">{{ 'footer.privacy' | translate }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dexar-footer-col">
|
<div class="dexar-footer-col">
|
||||||
<h4>Помощь</h4>
|
<h4>{{ 'footer.help' | translate }}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a [routerLink]="'/faq' | langRoute">FAQ</a></li>
|
<li><a [routerLink]="'/faq' | langRoute">{{ 'footer.faq' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/delivery' | langRoute">Доставка</a></li>
|
<li><a [routerLink]="'/delivery' | langRoute">{{ 'footer.delivery' | translate }}</a></li>
|
||||||
<li><a [routerLink]="'/guarantee' | langRoute">Гарантия</a></li>
|
<li><a [routerLink]="'/guarantee' | langRoute">{{ 'footer.guarantee' | translate }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dexar-footer-col">
|
<div class="dexar-footer-col">
|
||||||
<h4>Оплата</h4>
|
<h4>{{ 'footer.payment' | translate }}</h4>
|
||||||
<div class="dexar-payment-logos">
|
<div class="dexar-payment-logos">
|
||||||
<img src="/assets/images/mir-logo.svg" alt="МИР" loading="lazy" width="48" height="32" />
|
<img src="/assets/images/mir-logo.svg" alt="МИР" loading="lazy" width="48" height="32" />
|
||||||
<img src="/assets/images/visa-logo.svg" alt="Visa" loading="lazy" width="48" height="32" />
|
<img src="/assets/images/visa-logo.svg" alt="Visa" loading="lazy" width="48" height="32" />
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dexar-footer-bottom">
|
<div class="dexar-footer-bottom">
|
||||||
<p>© {{ currentYear }} {{ brandName }}. Все права защищены.</p>
|
<p>© {{ currentYear }} {{ brandName }}. {{ 'footer.allRightsReserved' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ 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';
|
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
||||||
|
import { TranslatePipe } from '../../i18n/translate.pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-footer',
|
selector: 'app-footer',
|
||||||
imports: [RouterLink, LangRoutePipe],
|
imports: [RouterLink, LangRoutePipe, TranslatePipe],
|
||||||
templateUrl: './footer.component.html',
|
templateUrl: './footer.component.html',
|
||||||
styleUrls: ['./footer.component.scss'],
|
styleUrls: ['./footer.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
|||||||
@@ -12,16 +12,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]="'/' | langRoute" 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">
|
||||||
Главная
|
{{ 'header.home' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<a [routerLink]="'/search' | langRoute" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link">
|
<a [routerLink]="'/search' | langRoute" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link">
|
||||||
Поиск
|
{{ 'header.search' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<a [routerLink]="'/about' | langRoute" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link">
|
<a [routerLink]="'/about' | langRoute" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link">
|
||||||
О нас
|
{{ 'header.about' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<a [routerLink]="'/contacts' | langRoute" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link">
|
<a [routerLink]="'/contacts' | langRoute" routerLinkActive="novo-active" (click)="closeMenu()" class="novo-link">
|
||||||
Контакты
|
{{ 'header.contacts' | translate }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -62,13 +62,13 @@
|
|||||||
<div class="dexar-nav-group">
|
<div class="dexar-nav-group">
|
||||||
<a [routerLink]="'/' | langRoute" 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">
|
||||||
Главная
|
{{ 'header.home' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<a [routerLink]="'/about' | langRoute" 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">
|
||||||
О нас
|
{{ 'header.about' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<a [routerLink]="'/contacts' | langRoute" 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">
|
||||||
Контакты
|
{{ 'header.contacts' | translate }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -80,12 +80,12 @@
|
|||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4ZM2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12Z" fill="#576463" />
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4ZM2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12Z" fill="#576463" />
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2929 18.2929C18.6834 17.9024 19.3166 17.9024 19.7071 18.2929L25.7071 24.2929C26.0976 24.6834 26.0976 25.3166 25.7071 25.7071C25.3166 26.0976 24.6834 26.0976 24.2929 25.7071L18.2929 19.7071C17.9024 19.3166 17.9024 18.6834 18.2929 18.2929Z" fill="#576463" />
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2929 18.2929C18.6834 17.9024 19.3166 17.9024 19.7071 18.2929L25.7071 24.2929C26.0976 24.6834 26.0976 25.3166 25.7071 25.7071C25.3166 26.0976 24.6834 26.0976 24.2929 25.7071L18.2929 19.7071C17.9024 19.3166 17.9024 18.6834 18.2929 18.2929Z" fill="#576463" />
|
||||||
</svg>
|
</svg>
|
||||||
<input type="text" placeholder="Искать..." class="dexar-search-input" (click)="navigateToSearch()" readonly />
|
<input type="text" [placeholder]="'header.searchPlaceholder' | translate" class="dexar-search-input" (click)="navigateToSearch()" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search Icon (mobile only) -->
|
<!-- Search Icon (mobile only) -->
|
||||||
<button class="dexar-search-mobile" (click)="navigateToSearch()" aria-label="Поиск">
|
<button class="dexar-search-mobile" (click)="navigateToSearch()" [attr.aria-label]="'header.search' | translate">
|
||||||
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4ZM2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12Z" fill="#1e3c38" />
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4ZM2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12Z" fill="#1e3c38" />
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2929 18.2929C18.6834 17.9024 19.3166 17.9024 19.7071 18.2929L25.7071 24.2929C26.0976 24.6834 26.0976 25.3166 25.7071 25.7071C25.3166 26.0976 24.6834 26.0976 24.2929 25.7071L18.2929 19.7071C17.9024 19.3166 17.9024 18.6834 18.2929 18.2929Z" fill="#1e3c38" />
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2929 18.2929C18.6834 17.9024 19.3166 17.9024 19.7071 18.2929L25.7071 24.2929C26.0976 24.6834 26.0976 25.3166 25.7071 25.7071C25.3166 26.0976 24.6834 26.0976 24.2929 25.7071L18.2929 19.7071C17.9024 19.3166 17.9024 18.6834 18.2929 18.2929Z" fill="#1e3c38" />
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
<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" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Главная</span>
|
<span>{{ 'header.home' | translate }}</span>
|
||||||
<svg class="dexar-mobile-chevron" width="8" height="14" viewBox="0 0 8 14" fill="none">
|
<svg class="dexar-mobile-chevron" width="8" height="14" viewBox="0 0 8 14" fill="none">
|
||||||
<path d="M1 1L7 7L1 13" stroke="#697777" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M1 1L7 7L1 13" stroke="#697777" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
<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="M1.9375 4.84375C1.9375 3.23867 3.23867 1.9375 4.84375 1.9375L10.6562 1.9375C12.2613 1.9375 13.5625 3.23867 13.5625 4.84375V10.6562C13.5625 12.2613 12.2613 13.5625 10.6562 13.5625H4.84375C3.23867 13.5625 1.9375 12.2613 1.9375 10.6562L1.9375 4.84375ZM4.84375 3.875C4.30872 3.875 3.875 4.30872 3.875 4.84375V10.6562C3.875 11.1913 4.30872 11.625 4.84375 11.625H10.6562C11.1913 11.625 11.625 11.1913 11.625 10.6562V4.84375C11.625 4.30872 11.1913 3.875 10.6562 3.875H4.84375ZM17.4375 4.84375C17.4375 3.23867 18.7387 1.9375 20.3438 1.9375L26.1562 1.9375C27.7613 1.9375 29.0625 3.23867 29.0625 4.84375V10.6562C29.0625 12.2613 27.7613 13.5625 26.1562 13.5625H20.3438C18.7387 13.5625 17.4375 12.2613 17.4375 10.6562V4.84375ZM20.3438 3.875C19.8087 3.875 19.375 4.30872 19.375 4.84375V10.6562C19.375 11.1913 19.8087 11.625 20.3438 11.625H26.1562C26.6913 11.625 27.125 11.1913 27.125 10.6562V4.84375C27.125 4.30872 26.6913 3.875 26.1562 3.875H20.3438ZM1.9375 20.3438C1.9375 18.7387 3.23867 17.4375 4.84375 17.4375H10.6562C12.2613 17.4375 13.5625 18.7387 13.5625 20.3438V26.1562C13.5625 27.7613 12.2613 29.0625 10.6562 29.0625H4.84375C3.23867 29.0625 1.9375 27.7613 1.9375 26.1562L1.9375 20.3438ZM4.84375 19.375C4.30872 19.375 3.875 19.8087 3.875 20.3438V26.1562C3.875 26.6913 4.30872 27.125 4.84375 27.125H10.6562C11.1913 27.125 11.625 26.6913 11.625 26.1562V20.3438C11.625 19.8087 11.1913 19.375 10.6562 19.375H4.84375ZM17.4375 20.3438C17.4375 18.7387 18.7387 17.4375 20.3438 17.4375H26.1562C27.7613 17.4375 29.0625 18.7387 29.0625 20.3438V26.1562C29.0625 27.7613 27.7613 29.0625 26.1562 29.0625H20.3438C18.7387 29.0625 17.4375 27.7613 17.4375 26.1562V20.3438ZM20.3438 19.375C19.8087 19.375 19.375 19.8087 19.375 20.3438V26.1562C19.375 26.6913 19.8087 27.125 20.3438 27.125H26.1562C26.6913 27.125 27.125 26.6913 27.125 26.1562V20.3438C27.125 19.8087 26.6913 19.375 26.1562 19.375H20.3438Z" fill="#497671" />
|
<path d="M1.9375 4.84375C1.9375 3.23867 3.23867 1.9375 4.84375 1.9375L10.6562 1.9375C12.2613 1.9375 13.5625 3.23867 13.5625 4.84375V10.6562C13.5625 12.2613 12.2613 13.5625 10.6562 13.5625H4.84375C3.23867 13.5625 1.9375 12.2613 1.9375 10.6562L1.9375 4.84375ZM4.84375 3.875C4.30872 3.875 3.875 4.30872 3.875 4.84375V10.6562C3.875 11.1913 4.30872 11.625 4.84375 11.625H10.6562C11.1913 11.625 11.625 11.1913 11.625 10.6562V4.84375C11.625 4.30872 11.1913 3.875 10.6562 3.875H4.84375ZM17.4375 4.84375C17.4375 3.23867 18.7387 1.9375 20.3438 1.9375L26.1562 1.9375C27.7613 1.9375 29.0625 3.23867 29.0625 4.84375V10.6562C29.0625 12.2613 27.7613 13.5625 26.1562 13.5625H20.3438C18.7387 13.5625 17.4375 12.2613 17.4375 10.6562V4.84375ZM20.3438 3.875C19.8087 3.875 19.375 4.30872 19.375 4.84375V10.6562C19.375 11.1913 19.8087 11.625 20.3438 11.625H26.1562C26.6913 11.625 27.125 11.1913 27.125 10.6562V4.84375C27.125 4.30872 26.6913 3.875 26.1562 3.875H20.3438ZM1.9375 20.3438C1.9375 18.7387 3.23867 17.4375 4.84375 17.4375H10.6562C12.2613 17.4375 13.5625 18.7387 13.5625 20.3438V26.1562C13.5625 27.7613 12.2613 29.0625 10.6562 29.0625H4.84375C3.23867 29.0625 1.9375 27.7613 1.9375 26.1562L1.9375 20.3438ZM4.84375 19.375C4.30872 19.375 3.875 19.8087 3.875 20.3438V26.1562C3.875 26.6913 4.30872 27.125 4.84375 27.125H10.6562C11.1913 27.125 11.625 26.6913 11.625 26.1562V20.3438C11.625 19.8087 11.1913 19.375 10.6562 19.375H4.84375ZM17.4375 20.3438C17.4375 18.7387 18.7387 17.4375 20.3438 17.4375H26.1562C27.7613 17.4375 29.0625 18.7387 29.0625 20.3438V26.1562C29.0625 27.7613 27.7613 29.0625 26.1562 29.0625H20.3438C18.7387 29.0625 17.4375 27.7613 17.4375 26.1562V20.3438ZM20.3438 19.375C19.8087 19.375 19.375 19.8087 19.375 20.3438V26.1562C19.375 26.6913 19.8087 27.125 20.3438 27.125H26.1562C26.6913 27.125 27.125 26.6913 27.125 26.1562V20.3438C27.125 19.8087 26.6913 19.375 26.1562 19.375H20.3438Z" fill="#497671" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Каталог</span>
|
<span>{{ 'header.catalog' | translate }}</span>
|
||||||
<svg class="dexar-mobile-chevron" width="8" height="14" viewBox="0 0 8 14" fill="none">
|
<svg class="dexar-mobile-chevron" width="8" height="14" viewBox="0 0 8 14" fill="none">
|
||||||
<path d="M1 1L7 7L1 13" stroke="#697777" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M1 1L7 7L1 13" stroke="#697777" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
<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" />
|
||||||
<path d="M17.4375 8.71875C17.4375 9.7888 16.5701 10.6562 15.5 10.6562C14.4299 10.6562 13.5625 9.7888 13.5625 8.71875C13.5625 7.6487 14.4299 6.78125 15.5 6.78125C16.5701 6.78125 17.4375 7.6487 17.4375 8.71875Z" fill="#497671" />
|
<path d="M17.4375 8.71875C17.4375 9.7888 16.5701 10.6562 15.5 10.6562C14.4299 10.6562 13.5625 9.7888 13.5625 8.71875C13.5625 7.6487 14.4299 6.78125 15.5 6.78125C16.5701 6.78125 17.4375 7.6487 17.4375 8.71875Z" fill="#497671" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>О нас</span>
|
<span>{{ 'header.about' | translate }}</span>
|
||||||
<svg class="dexar-mobile-chevron" width="8" height="14" viewBox="0 0 8 14" fill="none">
|
<svg class="dexar-mobile-chevron" width="8" height="14" viewBox="0 0 8 14" fill="none">
|
||||||
<path d="M1 1L7 7L1 13" stroke="#697777" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M1 1L7 7L1 13" stroke="#697777" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
<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>
|
||||||
<span>Контакты</span>
|
<span>{{ 'header.contacts' | translate }}</span>
|
||||||
<svg class="dexar-mobile-chevron" width="8" height="14" viewBox="0 0 8 14" fill="none">
|
<svg class="dexar-mobile-chevron" width="8" height="14" viewBox="0 0 8 14" fill="none">
|
||||||
<path d="M1 1L7 7L1 13" stroke="#697777" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M1 1L7 7L1 13" stroke="#697777" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ 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';
|
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
||||||
|
import { TranslatePipe } from '../../i18n/translate.pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
imports: [RouterLink, RouterLinkActive, LogoComponent, LanguageSelectorComponent, LangRoutePipe],
|
imports: [RouterLink, RouterLinkActive, LogoComponent, LanguageSelectorComponent, LangRoutePipe, TranslatePipe],
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrls: ['./header.component.scss'],
|
styleUrls: ['./header.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
<div class="carousel-loading">
|
<div class="carousel-loading">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Загрузка товаров...</p>
|
<p>{{ 'carousel.loading' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
} @else if (products().length > 0) {
|
} @else if (products().length > 0) {
|
||||||
<p-carousel
|
<p-carousel
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
<span class="current-price">{{ product.price }} {{ product.currency }}</span>
|
<span class="current-price">{{ product.price }} {{ product.currency }}</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<button class="cart-icon-btn" (click)="addToCart($event, product)" title="Добавить в корзину">
|
<button class="cart-icon-btn" (click)="addToCart($event, product)" [title]="'carousel.addToCart' | translate">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path>
|
<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path>
|
||||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ 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';
|
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
||||||
|
import { TranslatePipe } from '../../i18n/translate.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, LangRoutePipe],
|
imports: [DecimalPipe, RouterLink, CarouselModule, ButtonModule, TagModule, LangRoutePipe, TranslatePipe],
|
||||||
styleUrls: ['./items-carousel.component.scss'],
|
styleUrls: ['./items-carousel.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
|
|||||||
188
src/app/i18n/en.ts
Normal file
188
src/app/i18n/en.ts
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import { Translations } from './translations';
|
||||||
|
|
||||||
|
export const en: Translations = {
|
||||||
|
header: {
|
||||||
|
home: 'Home',
|
||||||
|
search: 'Search',
|
||||||
|
about: 'About',
|
||||||
|
contacts: 'Contacts',
|
||||||
|
searchPlaceholder: 'Search...',
|
||||||
|
catalog: 'Catalog',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
description: 'A modern marketplace for comfortable shopping',
|
||||||
|
company: 'Company',
|
||||||
|
aboutUs: 'About us',
|
||||||
|
contacts: 'Contacts',
|
||||||
|
requisites: 'Company details',
|
||||||
|
support: 'Support',
|
||||||
|
faq: 'FAQ',
|
||||||
|
delivery: 'Delivery',
|
||||||
|
guarantee: 'Warranty',
|
||||||
|
legal: 'Legal information',
|
||||||
|
offer: 'Public offer',
|
||||||
|
privacy: 'Privacy policy',
|
||||||
|
returns: 'Returns',
|
||||||
|
info: 'Information',
|
||||||
|
aboutCompany: 'About company',
|
||||||
|
documents: 'Documents',
|
||||||
|
paymentRules: 'Payment terms',
|
||||||
|
returnPolicy: 'Return policy',
|
||||||
|
publicOffer: 'Public offer',
|
||||||
|
help: 'Help',
|
||||||
|
payment: 'Payment',
|
||||||
|
allRightsReserved: 'All rights reserved.',
|
||||||
|
},
|
||||||
|
home: {
|
||||||
|
welcomeTo: 'Welcome to {{brand}}',
|
||||||
|
subtitle: 'Find everything you need in one place',
|
||||||
|
startSearch: 'Start search',
|
||||||
|
loading: 'Loading categories...',
|
||||||
|
errorTitle: 'Something went wrong',
|
||||||
|
retry: 'Try again',
|
||||||
|
categoriesTitle: 'Product categories',
|
||||||
|
categoriesSubtitle: 'Choose a category',
|
||||||
|
categoriesEmpty: 'Categories coming soon',
|
||||||
|
categoriesEmptyDesc: 'We are working on filling the catalog',
|
||||||
|
dexarHeroTitle: 'Find everything here',
|
||||||
|
dexarHeroSubtitle: 'Thousands of products in one place',
|
||||||
|
dexarHeroTagline: 'simple and convenient',
|
||||||
|
goToCatalog: 'Go to catalog',
|
||||||
|
findProduct: 'Find a product',
|
||||||
|
loadingDexar: 'Loading categories...',
|
||||||
|
catalogTitle: 'Product catalog',
|
||||||
|
emptyCategoriesDexar: 'No categories yet',
|
||||||
|
categoriesSoonDexar: 'Categories will appear here soon',
|
||||||
|
itemsCount: '{{count}} products',
|
||||||
|
},
|
||||||
|
cart: {
|
||||||
|
title: 'Cart',
|
||||||
|
clear: 'Clear',
|
||||||
|
empty: 'Cart is empty',
|
||||||
|
emptyDesc: 'Add products to start shopping',
|
||||||
|
goShopping: 'Start shopping',
|
||||||
|
total: 'Total',
|
||||||
|
items: 'Products',
|
||||||
|
deliveryLabel: 'Delivery',
|
||||||
|
toPay: 'To pay',
|
||||||
|
agreeWith: 'I agree with the',
|
||||||
|
publicOffer: 'public offer',
|
||||||
|
returnPolicy: 'return policy',
|
||||||
|
guaranteeTerms: 'warranty terms',
|
||||||
|
privacyPolicy: 'privacy policy',
|
||||||
|
and: 'and',
|
||||||
|
checkout: 'Place order',
|
||||||
|
close: 'Close',
|
||||||
|
creatingPayment: 'Creating payment...',
|
||||||
|
waitFewSeconds: 'Please wait a few seconds',
|
||||||
|
scanQr: 'Scan the QR code to pay',
|
||||||
|
amountToPay: 'Amount due:',
|
||||||
|
waitingPayment: 'Waiting for payment...',
|
||||||
|
copied: '✓ Copied',
|
||||||
|
copyLink: 'Copy link',
|
||||||
|
openNewTab: 'Open in new tab',
|
||||||
|
paymentSuccess: 'Congratulations! Payment successful!',
|
||||||
|
paymentSuccessDesc: 'Enter your contact details and we will send your purchase within a few minutes',
|
||||||
|
sending: 'Sending...',
|
||||||
|
send: 'Send',
|
||||||
|
paymentTimeout: 'Payment timed out',
|
||||||
|
paymentTimeoutDesc: 'We did not receive payment confirmation within 3 minutes.',
|
||||||
|
autoClose: 'Window will close automatically...',
|
||||||
|
confirmClear: 'Are you sure you want to clear the cart?',
|
||||||
|
acceptTerms: 'Please accept the offer terms, return policy, and warranty terms to proceed with the order.',
|
||||||
|
copyError: 'Copy error:',
|
||||||
|
emailSuccess: 'Email sent successfully! Check your inbox.',
|
||||||
|
emailError: 'An error occurred while sending the email. Please try again.',
|
||||||
|
phoneRequired: 'Phone number is required',
|
||||||
|
phoneMoreDigits: 'Enter {{count}} more digits',
|
||||||
|
phoneTooMany: 'Too many digits',
|
||||||
|
emailRequired: 'Email is required',
|
||||||
|
emailTooShort: 'Email is too short (minimum 5 characters)',
|
||||||
|
emailTooLong: 'Email is too long (maximum 100 characters)',
|
||||||
|
emailNeedsAt: 'Email must contain @',
|
||||||
|
emailNeedsDomain: 'Email must contain a domain (.com, .ru, etc.)',
|
||||||
|
emailInvalid: 'Invalid email format',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
title: 'Product search',
|
||||||
|
placeholder: 'Enter product name...',
|
||||||
|
resultsCount: 'Products found:',
|
||||||
|
searching: 'Searching...',
|
||||||
|
retry: 'Try again',
|
||||||
|
noResults: 'Nothing found',
|
||||||
|
noResultsFor: 'No products found for "{{query}}"',
|
||||||
|
noResultsHint: 'Try changing your query or using different keywords',
|
||||||
|
addToCart: 'Add to cart',
|
||||||
|
loadingMore: 'Loading...',
|
||||||
|
allLoaded: 'All results loaded',
|
||||||
|
emptyState: 'Enter a query to search for products',
|
||||||
|
of: 'of',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
retry: 'Try again',
|
||||||
|
addToCart: 'Add to cart',
|
||||||
|
loadingMore: 'Loading...',
|
||||||
|
allLoaded: 'All products loaded',
|
||||||
|
emptyTitle: 'Oops! Nothing here yet',
|
||||||
|
emptyDesc: 'There are no products in this category yet, but they will appear soon',
|
||||||
|
goHome: 'Go home',
|
||||||
|
loading: 'Loading products...',
|
||||||
|
},
|
||||||
|
subcategories: {
|
||||||
|
loading: 'Loading subcategories...',
|
||||||
|
retry: 'Try again',
|
||||||
|
emptyTitle: 'Oops! No subcategories yet',
|
||||||
|
emptyDesc: 'There are no subcategories in this section yet, but they will appear soon',
|
||||||
|
goHome: 'Go home',
|
||||||
|
},
|
||||||
|
itemDetail: {
|
||||||
|
loading: 'Loading...',
|
||||||
|
loadingDexar: 'Loading product...',
|
||||||
|
back: 'Go back',
|
||||||
|
backHome: 'Back to home',
|
||||||
|
noImage: 'No image',
|
||||||
|
stock: 'Availability:',
|
||||||
|
inStock: 'In stock',
|
||||||
|
lowStock: 'Few left',
|
||||||
|
lastItems: 'Last items',
|
||||||
|
mediumStock: 'Running low',
|
||||||
|
addToCart: 'Add to cart',
|
||||||
|
description: 'Description',
|
||||||
|
reviews: 'Reviews',
|
||||||
|
yourReview: 'Your review',
|
||||||
|
leaveReview: 'Leave a review',
|
||||||
|
rating: 'Rating:',
|
||||||
|
reviewPlaceholder: 'Share your experience with this product...',
|
||||||
|
reviewPlaceholderDexar: 'Share your thoughts on this product...',
|
||||||
|
anonymous: 'Anonymous',
|
||||||
|
submitting: 'Submitting...',
|
||||||
|
submit: 'Submit',
|
||||||
|
reviewSuccess: 'Thank you for your review!',
|
||||||
|
reviewError: 'Submission failed. Please try later.',
|
||||||
|
defaultUser: 'User',
|
||||||
|
defaultUserDexar: 'Anonymous',
|
||||||
|
noReviews: 'No reviews yet. Be the first!',
|
||||||
|
qna: 'Questions & Answers',
|
||||||
|
photo: 'Photo',
|
||||||
|
reviewsCount: 'reviews',
|
||||||
|
today: 'Today',
|
||||||
|
yesterday: 'Yesterday',
|
||||||
|
daysAgo: 'd. ago',
|
||||||
|
weeksAgo: 'w. ago',
|
||||||
|
},
|
||||||
|
app: {
|
||||||
|
connecting: 'Connecting to server...',
|
||||||
|
serverUnavailable: 'Server unavailable',
|
||||||
|
serverError: 'Could not connect to the server. Please check your internet connection.',
|
||||||
|
retryConnection: 'Retry',
|
||||||
|
pageTitle: 'Marketplace of goods and services',
|
||||||
|
},
|
||||||
|
carousel: {
|
||||||
|
loading: 'Loading products...',
|
||||||
|
addToCart: 'Add to cart',
|
||||||
|
},
|
||||||
|
common: {
|
||||||
|
retry: 'Try again',
|
||||||
|
loading: 'Loading...',
|
||||||
|
},
|
||||||
|
};
|
||||||
188
src/app/i18n/hy.ts
Normal file
188
src/app/i18n/hy.ts
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import { Translations } from './translations';
|
||||||
|
|
||||||
|
export const hy: Translations = {
|
||||||
|
header: {
|
||||||
|
home: 'Գլխավոր',
|
||||||
|
search: 'Որոնում',
|
||||||
|
about: 'Մեր մասին',
|
||||||
|
contacts: 'Կապ',
|
||||||
|
searchPlaceholder: 'Որոնել...',
|
||||||
|
catalog: 'Կատալոգ',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
description: 'Ժամանակակից մարքեթփլեյս հարմար գնումների համար',
|
||||||
|
company: 'Ընկերություն',
|
||||||
|
aboutUs: 'Մեր մասին',
|
||||||
|
contacts: 'Կապ',
|
||||||
|
requisites: 'Վավերապայմաններ',
|
||||||
|
support: 'Աջակցություն',
|
||||||
|
faq: 'ՀՏՀ',
|
||||||
|
delivery: 'Առաքում',
|
||||||
|
guarantee: 'Երաշխիք',
|
||||||
|
legal: 'Իրավական տեղեկատվություն',
|
||||||
|
offer: 'Օֆերտա',
|
||||||
|
privacy: 'Գաղտնիություն',
|
||||||
|
returns: 'Վերադարձ',
|
||||||
|
info: 'Տեղեկատվություն',
|
||||||
|
aboutCompany: 'Ընկերության մասին',
|
||||||
|
documents: 'Փաստաթղթեր',
|
||||||
|
paymentRules: 'Վճարման կանոններ',
|
||||||
|
returnPolicy: 'Վերադարձի քաղաքականություն',
|
||||||
|
publicOffer: 'Հանրային օֆերտա',
|
||||||
|
help: 'Օգնություն',
|
||||||
|
payment: 'Վճարում',
|
||||||
|
allRightsReserved: 'Բոլոր իրավունքները պաշտպանված են։',
|
||||||
|
},
|
||||||
|
home: {
|
||||||
|
welcomeTo: 'Բարի գալուստ {{brand}}',
|
||||||
|
subtitle: 'Գտեք ամեն ինչ մեկ վայրում',
|
||||||
|
startSearch: 'Սկսել որոնումը',
|
||||||
|
loading: 'Կատեգորիաները բեռնվում են...',
|
||||||
|
errorTitle: 'Ինչ-որ բան սխալ է գնացել',
|
||||||
|
retry: 'Փորձել կրկին',
|
||||||
|
categoriesTitle: 'Ապրանքների կատեգորիաներ',
|
||||||
|
categoriesSubtitle: 'Ընտրեք հետաքրքրող կատեգորիան',
|
||||||
|
categoriesEmpty: 'Կատեգորիաները շուտով կհայտնվեն',
|
||||||
|
categoriesEmptyDesc: 'Մենք աշխատում ենք կատալոգի համալրման վրա',
|
||||||
|
dexarHeroTitle: 'Այստեղ դու կգտնես ամեն ինչ',
|
||||||
|
dexarHeroSubtitle: 'Հազարավոր ապրանքներ մեկ վայրում',
|
||||||
|
dexarHeroTagline: 'պարզ և հարմար',
|
||||||
|
goToCatalog: 'Անցնել կատալոգ',
|
||||||
|
findProduct: 'Գտնել ապրանք',
|
||||||
|
loadingDexar: 'Կատեգորիաները բեռնվում են...',
|
||||||
|
catalogTitle: 'Ապրանքների կատալոգ',
|
||||||
|
emptyCategoriesDexar: 'Կատեգորիաները դեռ չկան',
|
||||||
|
categoriesSoonDexar: 'Շուտով այստեղ կհայտնվեն ապրանքների կատեգորիաներ',
|
||||||
|
itemsCount: '{{count}} ապրանք',
|
||||||
|
},
|
||||||
|
cart: {
|
||||||
|
title: 'Զամբյուղ',
|
||||||
|
clear: 'Մաքրել',
|
||||||
|
empty: 'Զամբյուղը դատարկ է',
|
||||||
|
emptyDesc: 'Ավելացրեք ապրանքներ գնումները սկսելու համար',
|
||||||
|
goShopping: 'Անցնել գնումների',
|
||||||
|
total: 'Ընդամենը',
|
||||||
|
items: 'Ապրանքներ',
|
||||||
|
deliveryLabel: 'Առաքում',
|
||||||
|
toPay: 'Վճարման ենթակա',
|
||||||
|
agreeWith: 'Ես համաձայն եմ',
|
||||||
|
publicOffer: 'հանրային օֆերտային',
|
||||||
|
returnPolicy: 'վերադարձի քաղաքականությանը',
|
||||||
|
guaranteeTerms: 'երաշխիքային պայմաններին',
|
||||||
|
privacyPolicy: 'գաղտնիության քաղաքականությանը',
|
||||||
|
and: 'և',
|
||||||
|
checkout: 'Ձևակերպել պատվեր',
|
||||||
|
close: 'Փակել',
|
||||||
|
creatingPayment: 'Վճարումը ստեղծվում է...',
|
||||||
|
waitFewSeconds: 'Սպասեք մի քանի վայրկյան',
|
||||||
|
scanQr: 'Սկանավորեք QR կոդը վճարման համար',
|
||||||
|
amountToPay: 'Վճարման գումարը՝',
|
||||||
|
waitingPayment: 'Սպասում ենք վճարմանը...',
|
||||||
|
copied: '✓ Պատճենված է',
|
||||||
|
copyLink: 'Պատճենել հղումը',
|
||||||
|
openNewTab: 'Բացել նոր ներդիրում',
|
||||||
|
paymentSuccess: 'Շնորհավորում ենք։ Վճարումը հաջողությամբ կատարվել է։',
|
||||||
|
paymentSuccessDesc: 'Մուտքագրեք ձեր կոնտակտային տվյալները, և մենք կուղարկենք գնումը մի քանի րոպեի ընթացքում',
|
||||||
|
sending: 'Ուղարկվում է...',
|
||||||
|
send: 'Ուղարկել',
|
||||||
|
paymentTimeout: 'Սպասման ժամանակը սպառվել է',
|
||||||
|
paymentTimeoutDesc: 'Մենք չենք ստացել վճարման հաստատում 3 րոպեի ընթացքում։',
|
||||||
|
autoClose: 'Պատուհանը կփակվի ավտոմատ...',
|
||||||
|
confirmClear: 'Համոզվա՞ծ եք, որ ցանկանում եք մաքրել զամբյուղը։',
|
||||||
|
acceptTerms: 'Խնդրում ենք ընդունել օֆերտայի, վերադարձի և երաշխիքի պայմանները պատվերը հաստատելու համար։',
|
||||||
|
copyError: 'Պատճենման սխալ՝',
|
||||||
|
emailSuccess: 'Email-ը հաջողությամբ ուղարկվել է։ Ստուգեք ձեր փոստը։',
|
||||||
|
emailError: 'Email ուղարկելու ժամանակ տեղի ունեցավ սխալ։ Խնդրում ենք փորձել կրկին։',
|
||||||
|
phoneRequired: 'Հեռախոսահամարը պարտադիր է',
|
||||||
|
phoneMoreDigits: 'Մուտքագրեք ևս {{count}} թիվ',
|
||||||
|
phoneTooMany: 'Չափազանց շատ թվեր',
|
||||||
|
emailRequired: 'Email-ը պարտադիր է',
|
||||||
|
emailTooShort: 'Email-ը չափազանց կարճ է (նվազագույնը 5 նիշ)',
|
||||||
|
emailTooLong: 'Email-ը չափազանց երկար է (առավելագույնը 100 նիշ)',
|
||||||
|
emailNeedsAt: 'Email-ը պետք է պարունակի @ նշանը',
|
||||||
|
emailNeedsDomain: 'Email-ը պետք է պարունակի դոմեն (.com, .ru և այլն)',
|
||||||
|
emailInvalid: 'Email-ի ձևաչափը սխալ է',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
title: 'Ապրանքների որոնում',
|
||||||
|
placeholder: 'Մուտքագրեք ապրանքի անունը...',
|
||||||
|
resultsCount: 'Գտնված ապրանքներ՝',
|
||||||
|
searching: 'Որոնում...',
|
||||||
|
retry: 'Փորձել կրկին',
|
||||||
|
noResults: 'Ոչինչ չի գտնվել',
|
||||||
|
noResultsFor: '"{{query}}" հարցման համար ապրանքներ չեն գտնվել',
|
||||||
|
noResultsHint: 'Փորձեք փոխել հարցումը կամ օգտագործել այլ բանալի բառեր',
|
||||||
|
addToCart: 'Ավելացնել զամբյուղ',
|
||||||
|
loadingMore: 'Բեռնվում է...',
|
||||||
|
allLoaded: 'Բոլոր արդյունքները բեռնված են',
|
||||||
|
emptyState: 'Մուտքագրեք հարցում ապրանքներ որոնելու համար',
|
||||||
|
of: 'ից',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
retry: 'Փորձել կրկին',
|
||||||
|
addToCart: 'Ավելացնել զամբյուղ',
|
||||||
|
loadingMore: 'Բեռնվում է...',
|
||||||
|
allLoaded: 'Բոլոր ապրանքները բեռնված են',
|
||||||
|
emptyTitle: 'Ուպս։ Այստեղ դեռ դատարկ է',
|
||||||
|
emptyDesc: 'Այս կատեգորիայում դեռ ապրանքներ չկան, բայց շուտով կհայտնվեն',
|
||||||
|
goHome: 'Գլխավոր էջ',
|
||||||
|
loading: 'Ապրանքները բեռնվում են...',
|
||||||
|
},
|
||||||
|
subcategories: {
|
||||||
|
loading: 'Ենթակատեգորիաները բեռնվում են...',
|
||||||
|
retry: 'Փորձել կրկին',
|
||||||
|
emptyTitle: 'Ուպս։ Ենթակատեգորիաներ դեռ չկան',
|
||||||
|
emptyDesc: 'Այս բաժնում դեռ ենթակատեգորիաներ չկան, բայց շուտով կհայտնվեն',
|
||||||
|
goHome: 'Գլխավոր էջ',
|
||||||
|
},
|
||||||
|
itemDetail: {
|
||||||
|
loading: 'Բեռնվում է...',
|
||||||
|
loadingDexar: 'Ապրանքը բեռնվում է...',
|
||||||
|
back: 'Վերադառնալ',
|
||||||
|
backHome: 'Վերադառնալ գլխավոր էջ',
|
||||||
|
noImage: 'Պատկեր չկա',
|
||||||
|
stock: 'Առկայություն՝',
|
||||||
|
inStock: 'Առկա է',
|
||||||
|
lowStock: 'Մնացել է քիչ',
|
||||||
|
lastItems: 'Վերջին հատերը',
|
||||||
|
mediumStock: 'Վերջանում է',
|
||||||
|
addToCart: 'Ավելացնել զամբյուղ',
|
||||||
|
description: 'Նկարագրություն',
|
||||||
|
reviews: 'Կարծիքներ',
|
||||||
|
yourReview: 'Ձեր կարծիքը',
|
||||||
|
leaveReview: 'Թողնել կարծիք',
|
||||||
|
rating: 'Գնահատական՝',
|
||||||
|
reviewPlaceholder: 'Կիսվեք ձեր տպավորություններով ապրանքի մասին...',
|
||||||
|
reviewPlaceholderDexar: 'Կիսվեք ձեր տպավորություններով...',
|
||||||
|
anonymous: 'Անանուն',
|
||||||
|
submitting: 'Ուղարկվում է...',
|
||||||
|
submit: 'Ուղարկել',
|
||||||
|
reviewSuccess: 'Շնորհակալություն ձեր կարծիքի համար։',
|
||||||
|
reviewError: 'Ուղարկման սխալ։ Փորձեք ավելի ուշ։',
|
||||||
|
defaultUser: 'Օգտատեր',
|
||||||
|
defaultUserDexar: 'Անանուն',
|
||||||
|
noReviews: 'Դեռ կարծիքներ չկան։ Դարձեք առաջինը։',
|
||||||
|
qna: 'Հարցեր և պատասխաններ',
|
||||||
|
photo: 'Լուսանկար',
|
||||||
|
reviewsCount: 'կարծիք',
|
||||||
|
today: 'Այսօր',
|
||||||
|
yesterday: 'Երեկ',
|
||||||
|
daysAgo: 'օր առաջ',
|
||||||
|
weeksAgo: 'շաբաթ առաջ',
|
||||||
|
},
|
||||||
|
app: {
|
||||||
|
connecting: 'Միացում սերվերին...',
|
||||||
|
serverUnavailable: 'Սերվերը հասանելի չէ',
|
||||||
|
serverError: 'Չհաջողվեց միանալ սերվերին։ Ստուգեք ինտերնետ կապը։',
|
||||||
|
retryConnection: 'Կրկնել փորձը',
|
||||||
|
pageTitle: 'Ապրանքների և ծառայությունների մարքեթփլեյս',
|
||||||
|
},
|
||||||
|
carousel: {
|
||||||
|
loading: 'Ապրանքները բեռնվում են...',
|
||||||
|
addToCart: 'Ավելացնել զամբյուղ',
|
||||||
|
},
|
||||||
|
common: {
|
||||||
|
retry: 'Փորձել կրկին',
|
||||||
|
loading: 'Բեռնվում է...',
|
||||||
|
},
|
||||||
|
};
|
||||||
188
src/app/i18n/ru.ts
Normal file
188
src/app/i18n/ru.ts
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import { Translations } from './translations';
|
||||||
|
|
||||||
|
export const ru: Translations = {
|
||||||
|
header: {
|
||||||
|
home: 'Главная',
|
||||||
|
search: 'Поиск',
|
||||||
|
about: 'О нас',
|
||||||
|
contacts: 'Контакты',
|
||||||
|
searchPlaceholder: 'Искать...',
|
||||||
|
catalog: 'Каталог',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
description: 'Современный маркетплейс для комфортных покупок',
|
||||||
|
company: 'Компания',
|
||||||
|
aboutUs: 'О нас',
|
||||||
|
contacts: 'Контакты',
|
||||||
|
requisites: 'Реквизиты',
|
||||||
|
support: 'Поддержка',
|
||||||
|
faq: 'FAQ',
|
||||||
|
delivery: 'Доставка',
|
||||||
|
guarantee: 'Гарантия',
|
||||||
|
legal: 'Правовая информация',
|
||||||
|
offer: 'Оферта',
|
||||||
|
privacy: 'Конфиденциальность',
|
||||||
|
returns: 'Возврат',
|
||||||
|
info: 'Информация',
|
||||||
|
aboutCompany: 'О компании',
|
||||||
|
documents: 'Документы',
|
||||||
|
paymentRules: 'Правила оплаты',
|
||||||
|
returnPolicy: 'Политика возврата',
|
||||||
|
publicOffer: 'Публичная оферта',
|
||||||
|
help: 'Помощь',
|
||||||
|
payment: 'Оплата',
|
||||||
|
allRightsReserved: 'Все права защищены.',
|
||||||
|
},
|
||||||
|
home: {
|
||||||
|
welcomeTo: 'Добро пожаловать в {{brand}}',
|
||||||
|
subtitle: 'Найдите всё, что нужно, в одном месте',
|
||||||
|
startSearch: 'Начать поиск',
|
||||||
|
loading: 'Загружаем категории...',
|
||||||
|
errorTitle: 'Что-то пошло не так',
|
||||||
|
retry: 'Попробовать снова',
|
||||||
|
categoriesTitle: 'Категории товаров',
|
||||||
|
categoriesSubtitle: 'Выберите интересующую категорию',
|
||||||
|
categoriesEmpty: 'Категории скоро появятся',
|
||||||
|
categoriesEmptyDesc: 'Мы работаем над наполнением каталога',
|
||||||
|
dexarHeroTitle: 'Здесь ты найдёшь всё',
|
||||||
|
dexarHeroSubtitle: 'Тысячи товаров в одном месте',
|
||||||
|
dexarHeroTagline: 'просто и удобно',
|
||||||
|
goToCatalog: 'Перейти в каталог',
|
||||||
|
findProduct: 'Найти товар',
|
||||||
|
loadingDexar: 'Загрузка категорий...',
|
||||||
|
catalogTitle: 'Каталог товаров',
|
||||||
|
emptyCategoriesDexar: 'Категории пока отсутствуют',
|
||||||
|
categoriesSoonDexar: 'Скоро здесь появятся категории товаров',
|
||||||
|
itemsCount: '{{count}} товаров',
|
||||||
|
},
|
||||||
|
cart: {
|
||||||
|
title: 'Корзина',
|
||||||
|
clear: 'Очистить',
|
||||||
|
empty: 'Корзина пуста',
|
||||||
|
emptyDesc: 'Добавьте товары, чтобы начать покупки',
|
||||||
|
goShopping: 'Перейти к покупкам',
|
||||||
|
total: 'Итого',
|
||||||
|
items: 'Товары',
|
||||||
|
deliveryLabel: 'Доставка',
|
||||||
|
toPay: 'К оплате',
|
||||||
|
agreeWith: 'Я согласен с',
|
||||||
|
publicOffer: 'публичной офертой',
|
||||||
|
returnPolicy: 'политикой возврата',
|
||||||
|
guaranteeTerms: 'условиями гарантии',
|
||||||
|
privacyPolicy: 'политикой конфиденциальности',
|
||||||
|
and: 'и',
|
||||||
|
checkout: 'Оформить заказ',
|
||||||
|
close: 'Закрыть',
|
||||||
|
creatingPayment: 'Создание платежа...',
|
||||||
|
waitFewSeconds: 'Подождите несколько секунд',
|
||||||
|
scanQr: 'Сканируйте QR-код для оплаты',
|
||||||
|
amountToPay: 'Сумма к оплате:',
|
||||||
|
waitingPayment: 'Ожидание оплаты...',
|
||||||
|
copied: '✓ Скопировано',
|
||||||
|
copyLink: 'Скопировать ссылку',
|
||||||
|
openNewTab: 'Открыть в новой вкладке',
|
||||||
|
paymentSuccess: 'Поздравляем! Оплата прошла успешно!',
|
||||||
|
paymentSuccessDesc: 'Введите ваши контактные данные, и мы отправим вам покупку в течение нескольких минут',
|
||||||
|
sending: 'Отправка...',
|
||||||
|
send: 'Отправить',
|
||||||
|
paymentTimeout: 'Время ожидания истекло',
|
||||||
|
paymentTimeoutDesc: 'Мы не получили подтверждение оплаты в течение 3 минут.',
|
||||||
|
autoClose: 'Окно закроется автоматически...',
|
||||||
|
confirmClear: 'Вы уверены, что хотите очистить корзину?',
|
||||||
|
acceptTerms: 'Пожалуйста, примите условия оферты, политику возврата и возврата для подтверждения оформления заказа.',
|
||||||
|
copyError: 'Ошибка копирования:',
|
||||||
|
emailSuccess: 'Email успешно отправлен! Проверьте свою почту.',
|
||||||
|
emailError: 'Произошла ошибка при отправке email. Пожалуйста, попробуйте снова.',
|
||||||
|
phoneRequired: 'Номер телефона обязателен',
|
||||||
|
phoneMoreDigits: 'Введите ещё {{count}} цифр',
|
||||||
|
phoneTooMany: 'Слишком много цифр',
|
||||||
|
emailRequired: 'Email обязателен',
|
||||||
|
emailTooShort: 'Email слишком короткий (минимум 5 символов)',
|
||||||
|
emailTooLong: 'Email слишком длинный (максимум 100 символов)',
|
||||||
|
emailNeedsAt: 'Email должен содержать @',
|
||||||
|
emailNeedsDomain: 'Email должен содержать домен (.com, .ru и т.д.)',
|
||||||
|
emailInvalid: 'Некорректный формат email',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
title: 'Поиск товаров',
|
||||||
|
placeholder: 'Введите название товара...',
|
||||||
|
resultsCount: 'Найдено товаров:',
|
||||||
|
searching: 'Поиск...',
|
||||||
|
retry: 'Попробовать снова',
|
||||||
|
noResults: 'Ничего не найдено',
|
||||||
|
noResultsFor: 'По запросу "{{query}}" товары не найдены',
|
||||||
|
noResultsHint: 'Попробуйте изменить запрос или используйте другие ключевые слова',
|
||||||
|
addToCart: 'В корзину',
|
||||||
|
loadingMore: 'Загрузка...',
|
||||||
|
allLoaded: 'Все результаты загружены',
|
||||||
|
emptyState: 'Введите запрос для поиска товаров',
|
||||||
|
of: 'из',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
retry: 'Попробовать снова',
|
||||||
|
addToCart: 'В корзину',
|
||||||
|
loadingMore: 'Загрузка...',
|
||||||
|
allLoaded: 'Все товары загружены',
|
||||||
|
emptyTitle: 'Упс! Здесь пока пусто',
|
||||||
|
emptyDesc: 'В этой категории ещё нет товаров, но скоро они появятся',
|
||||||
|
goHome: 'На главную',
|
||||||
|
loading: 'Загрузка товаров...',
|
||||||
|
},
|
||||||
|
subcategories: {
|
||||||
|
loading: 'Загрузка подкатегорий...',
|
||||||
|
retry: 'Попробовать снова',
|
||||||
|
emptyTitle: 'Упс! Подкатегорий пока нет',
|
||||||
|
emptyDesc: 'В этом разделе ещё нет подкатегорий, но скоро они появятся',
|
||||||
|
goHome: 'На главную',
|
||||||
|
},
|
||||||
|
itemDetail: {
|
||||||
|
loading: 'Загрузка...',
|
||||||
|
loadingDexar: 'Загрузка товара...',
|
||||||
|
back: 'Вернуться',
|
||||||
|
backHome: 'Вернуться на главную',
|
||||||
|
noImage: 'Нет изображения',
|
||||||
|
stock: 'Наличие:',
|
||||||
|
inStock: 'В наличии',
|
||||||
|
lowStock: 'Осталось немного',
|
||||||
|
lastItems: 'Последние штуки',
|
||||||
|
mediumStock: 'Заканчивается',
|
||||||
|
addToCart: 'Добавить в корзину',
|
||||||
|
description: 'Описание',
|
||||||
|
reviews: 'Отзывы',
|
||||||
|
yourReview: 'Ваш отзыв',
|
||||||
|
leaveReview: 'Оставить отзыв',
|
||||||
|
rating: 'Оценка:',
|
||||||
|
reviewPlaceholder: 'Поделитесь своими впечатлениями о товаре...',
|
||||||
|
reviewPlaceholderDexar: 'Поделитесь впечатлениями о товаре...',
|
||||||
|
anonymous: 'Анонимно',
|
||||||
|
submitting: 'Отправка...',
|
||||||
|
submit: 'Отправить',
|
||||||
|
reviewSuccess: 'Спасибо за ваш отзыв!',
|
||||||
|
reviewError: 'Ошибка отправки. Попробуйте позже.',
|
||||||
|
defaultUser: 'Пользователь',
|
||||||
|
defaultUserDexar: 'Аноним',
|
||||||
|
noReviews: 'Пока нет отзывов. Станьте первым!',
|
||||||
|
qna: 'Вопросы и ответы',
|
||||||
|
photo: 'Фото',
|
||||||
|
reviewsCount: 'отзывов',
|
||||||
|
today: 'Сегодня',
|
||||||
|
yesterday: 'Вчера',
|
||||||
|
daysAgo: 'дн. назад',
|
||||||
|
weeksAgo: 'нед. назад',
|
||||||
|
},
|
||||||
|
app: {
|
||||||
|
connecting: 'Подключение к серверу...',
|
||||||
|
serverUnavailable: 'Сервер недоступен',
|
||||||
|
serverError: 'Не удалось подключиться к серверу. Проверьте подключение к интернету.',
|
||||||
|
retryConnection: 'Повторить попытку',
|
||||||
|
pageTitle: 'Маркетплейс товаров и услуг',
|
||||||
|
},
|
||||||
|
carousel: {
|
||||||
|
loading: 'Загрузка товаров...',
|
||||||
|
addToCart: 'Добавить в корзину',
|
||||||
|
},
|
||||||
|
common: {
|
||||||
|
retry: 'Попробовать снова',
|
||||||
|
loading: 'Загрузка...',
|
||||||
|
},
|
||||||
|
};
|
||||||
14
src/app/i18n/translate.pipe.ts
Normal file
14
src/app/i18n/translate.pipe.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Pipe, PipeTransform, inject } from '@angular/core';
|
||||||
|
import { TranslateService } from './translate.service';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'translate',
|
||||||
|
pure: false,
|
||||||
|
})
|
||||||
|
export class TranslatePipe implements PipeTransform {
|
||||||
|
private translateService = inject(TranslateService);
|
||||||
|
|
||||||
|
transform(key: string, params?: Record<string, string | number>): string {
|
||||||
|
return this.translateService.t(key, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/app/i18n/translate.service.ts
Normal file
48
src/app/i18n/translate.service.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Injectable, computed, inject } from '@angular/core';
|
||||||
|
import { LanguageService } from '../services/language.service';
|
||||||
|
import { Translations } from './translations';
|
||||||
|
import { ru } from './ru';
|
||||||
|
import { en } from './en';
|
||||||
|
import { hy } from './hy';
|
||||||
|
|
||||||
|
const translationMap: Record<string, Translations> = { ru, en, hy };
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class TranslateService {
|
||||||
|
private langService = inject(LanguageService);
|
||||||
|
|
||||||
|
readonly translations = computed<Translations>(
|
||||||
|
() => translationMap[this.langService.currentLanguage()] ?? ru,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a dot-separated key with optional interpolation params.
|
||||||
|
* Usage: t('cart.phoneMoreDigits', { count: 3 }) → "Введите ещё 3 цифр"
|
||||||
|
*/
|
||||||
|
t(key: string, params?: Record<string, string | number>): string {
|
||||||
|
const parts = key.split('.');
|
||||||
|
let result: unknown = this.translations();
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
if (result && typeof result === 'object') {
|
||||||
|
result = (result as Record<string, unknown>)[part];
|
||||||
|
} else {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof result !== 'string') {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params) {
|
||||||
|
return result.replace(/\{\{(\w+)\}\}/g, (_, k) =>
|
||||||
|
params[k] !== undefined ? String(params[k]) : `{{${k}}}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
186
src/app/i18n/translations.ts
Normal file
186
src/app/i18n/translations.ts
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
export interface Translations {
|
||||||
|
header: {
|
||||||
|
home: string;
|
||||||
|
search: string;
|
||||||
|
about: string;
|
||||||
|
contacts: string;
|
||||||
|
searchPlaceholder: string;
|
||||||
|
catalog: string;
|
||||||
|
};
|
||||||
|
footer: {
|
||||||
|
description: string;
|
||||||
|
company: string;
|
||||||
|
aboutUs: string;
|
||||||
|
contacts: string;
|
||||||
|
requisites: string;
|
||||||
|
support: string;
|
||||||
|
faq: string;
|
||||||
|
delivery: string;
|
||||||
|
guarantee: string;
|
||||||
|
legal: string;
|
||||||
|
offer: string;
|
||||||
|
privacy: string;
|
||||||
|
returns: string;
|
||||||
|
info: string;
|
||||||
|
aboutCompany: string;
|
||||||
|
documents: string;
|
||||||
|
paymentRules: string;
|
||||||
|
returnPolicy: string;
|
||||||
|
publicOffer: string;
|
||||||
|
help: string;
|
||||||
|
payment: string;
|
||||||
|
allRightsReserved: string;
|
||||||
|
};
|
||||||
|
home: {
|
||||||
|
welcomeTo: string;
|
||||||
|
subtitle: string;
|
||||||
|
startSearch: string;
|
||||||
|
loading: string;
|
||||||
|
errorTitle: string;
|
||||||
|
retry: string;
|
||||||
|
categoriesTitle: string;
|
||||||
|
categoriesSubtitle: string;
|
||||||
|
categoriesEmpty: string;
|
||||||
|
categoriesEmptyDesc: string;
|
||||||
|
dexarHeroTitle: string;
|
||||||
|
dexarHeroSubtitle: string;
|
||||||
|
dexarHeroTagline: string;
|
||||||
|
goToCatalog: string;
|
||||||
|
findProduct: string;
|
||||||
|
loadingDexar: string;
|
||||||
|
catalogTitle: string;
|
||||||
|
emptyCategoriesDexar: string;
|
||||||
|
categoriesSoonDexar: string;
|
||||||
|
itemsCount: string;
|
||||||
|
};
|
||||||
|
cart: {
|
||||||
|
title: string;
|
||||||
|
clear: string;
|
||||||
|
empty: string;
|
||||||
|
emptyDesc: string;
|
||||||
|
goShopping: string;
|
||||||
|
total: string;
|
||||||
|
items: string;
|
||||||
|
deliveryLabel: string;
|
||||||
|
toPay: string;
|
||||||
|
agreeWith: string;
|
||||||
|
publicOffer: string;
|
||||||
|
returnPolicy: string;
|
||||||
|
guaranteeTerms: string;
|
||||||
|
privacyPolicy: string;
|
||||||
|
and: string;
|
||||||
|
checkout: string;
|
||||||
|
close: string;
|
||||||
|
creatingPayment: string;
|
||||||
|
waitFewSeconds: string;
|
||||||
|
scanQr: string;
|
||||||
|
amountToPay: string;
|
||||||
|
waitingPayment: string;
|
||||||
|
copied: string;
|
||||||
|
copyLink: string;
|
||||||
|
openNewTab: string;
|
||||||
|
paymentSuccess: string;
|
||||||
|
paymentSuccessDesc: string;
|
||||||
|
sending: string;
|
||||||
|
send: string;
|
||||||
|
paymentTimeout: string;
|
||||||
|
paymentTimeoutDesc: string;
|
||||||
|
autoClose: string;
|
||||||
|
confirmClear: string;
|
||||||
|
acceptTerms: string;
|
||||||
|
copyError: string;
|
||||||
|
emailSuccess: string;
|
||||||
|
emailError: string;
|
||||||
|
phoneRequired: string;
|
||||||
|
phoneMoreDigits: string;
|
||||||
|
phoneTooMany: string;
|
||||||
|
emailRequired: string;
|
||||||
|
emailTooShort: string;
|
||||||
|
emailTooLong: string;
|
||||||
|
emailNeedsAt: string;
|
||||||
|
emailNeedsDomain: string;
|
||||||
|
emailInvalid: string;
|
||||||
|
};
|
||||||
|
search: {
|
||||||
|
title: string;
|
||||||
|
placeholder: string;
|
||||||
|
resultsCount: string;
|
||||||
|
searching: string;
|
||||||
|
retry: string;
|
||||||
|
noResults: string;
|
||||||
|
noResultsFor: string;
|
||||||
|
noResultsHint: string;
|
||||||
|
addToCart: string;
|
||||||
|
loadingMore: string;
|
||||||
|
allLoaded: string;
|
||||||
|
emptyState: string;
|
||||||
|
of: string;
|
||||||
|
};
|
||||||
|
category: {
|
||||||
|
retry: string;
|
||||||
|
addToCart: string;
|
||||||
|
loadingMore: string;
|
||||||
|
allLoaded: string;
|
||||||
|
emptyTitle: string;
|
||||||
|
emptyDesc: string;
|
||||||
|
goHome: string;
|
||||||
|
loading: string;
|
||||||
|
};
|
||||||
|
subcategories: {
|
||||||
|
loading: string;
|
||||||
|
retry: string;
|
||||||
|
emptyTitle: string;
|
||||||
|
emptyDesc: string;
|
||||||
|
goHome: string;
|
||||||
|
};
|
||||||
|
itemDetail: {
|
||||||
|
loading: string;
|
||||||
|
loadingDexar: string;
|
||||||
|
back: string;
|
||||||
|
backHome: string;
|
||||||
|
noImage: string;
|
||||||
|
stock: string;
|
||||||
|
inStock: string;
|
||||||
|
lowStock: string;
|
||||||
|
lastItems: string;
|
||||||
|
mediumStock: string;
|
||||||
|
addToCart: string;
|
||||||
|
description: string;
|
||||||
|
reviews: string;
|
||||||
|
yourReview: string;
|
||||||
|
leaveReview: string;
|
||||||
|
rating: string;
|
||||||
|
reviewPlaceholder: string;
|
||||||
|
reviewPlaceholderDexar: string;
|
||||||
|
anonymous: string;
|
||||||
|
submitting: string;
|
||||||
|
submit: string;
|
||||||
|
reviewSuccess: string;
|
||||||
|
reviewError: string;
|
||||||
|
defaultUser: string;
|
||||||
|
defaultUserDexar: string;
|
||||||
|
noReviews: string;
|
||||||
|
qna: string;
|
||||||
|
photo: string;
|
||||||
|
reviewsCount: string;
|
||||||
|
today: string;
|
||||||
|
yesterday: string;
|
||||||
|
daysAgo: string;
|
||||||
|
weeksAgo: string;
|
||||||
|
};
|
||||||
|
app: {
|
||||||
|
connecting: string;
|
||||||
|
serverUnavailable: string;
|
||||||
|
serverError: string;
|
||||||
|
retryConnection: string;
|
||||||
|
pageTitle: string;
|
||||||
|
};
|
||||||
|
carousel: {
|
||||||
|
loading: string;
|
||||||
|
addToCart: string;
|
||||||
|
};
|
||||||
|
common: {
|
||||||
|
retry: string;
|
||||||
|
loading: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
<div [class]="isnovo ? 'cart-container novo' : 'cart-container dexar'">
|
<div [class]="isnovo ? 'cart-container novo' : 'cart-container dexar'">
|
||||||
<div class="cart-header">
|
<div class="cart-header">
|
||||||
<h1>Корзина</h1>
|
<h1>{{ 'cart.title' | translate }}</h1>
|
||||||
@if (itemCount() > 0) {
|
@if (itemCount() > 0) {
|
||||||
<button class="clear-cart-btn" (click)="clearCart()">
|
<button class="clear-cart-btn" (click)="clearCart()">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
<path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
|
||||||
</svg>
|
</svg>
|
||||||
Очистить
|
{{ 'cart.clear' | translate }}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -16,9 +16,9 @@
|
|||||||
<div class="empty-icon">
|
<div class="empty-icon">
|
||||||
<app-empty-cart-icon />
|
<app-empty-cart-icon />
|
||||||
</div>
|
</div>
|
||||||
<h2>Корзина пуста</h2>
|
<h2>{{ 'cart.empty' | translate }}</h2>
|
||||||
<p>Добавьте товары, чтобы начать покупки</p>
|
<p>{{ 'cart.emptyDesc' | translate }}</p>
|
||||||
<a [routerLink]="'/' | langRoute" class="shop-btn">Перейти к покупкам</a>
|
<a [routerLink]="'/' | langRoute" class="shop-btn">{{ 'cart.goShopping' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,21 +86,21 @@
|
|||||||
|
|
||||||
<div class="cart-summary">
|
<div class="cart-summary">
|
||||||
<div class="summary-header">
|
<div class="summary-header">
|
||||||
<h3>Итого</h3>
|
<h3>{{ 'cart.total' | translate }}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="summary-row">
|
<div class="summary-row">
|
||||||
<span>Товары ({{ itemCount() }})</span>
|
<span>{{ 'cart.items' | translate }} ({{ itemCount() }})</span>
|
||||||
<span class="value">{{ totalPrice() | number:'1.2-2' }} ₽</span>
|
<span class="value">{{ totalPrice() | number:'1.2-2' }} ₽</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="summary-row delivery">
|
<div class="summary-row delivery">
|
||||||
<span>Доставка</span>
|
<span>{{ 'cart.deliveryLabel' | translate }}</span>
|
||||||
<span>0 ₽</span>
|
<span>0 ₽</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="summary-row total">
|
<div class="summary-row total">
|
||||||
<span>К оплате</span>
|
<span>{{ 'cart.toPay' | translate }}</span>
|
||||||
<span class="total-price">{{ totalPrice() | number:'1.2-2' }} ₽</span>
|
<span class="total-price">{{ totalPrice() | number:'1.2-2' }} ₽</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -113,11 +113,11 @@
|
|||||||
/>
|
/>
|
||||||
<span class="checkmark"></span>
|
<span class="checkmark"></span>
|
||||||
<span class="terms-text">
|
<span class="terms-text">
|
||||||
Я согласен с
|
{{ 'cart.agreeWith' | translate }}
|
||||||
<a [routerLink]="'/public-offer' | langRoute" target="_blank">публичной офертой</a>,
|
<a [routerLink]="'/public-offer' | langRoute" target="_blank">{{ 'cart.publicOffer' | translate }}</a>,
|
||||||
<a [routerLink]="'/return-policy' | langRoute" target="_blank">политикой возврата</a>,
|
<a [routerLink]="'/return-policy' | langRoute" target="_blank">{{ 'cart.returnPolicy' | translate }}</a>,
|
||||||
<a [routerLink]="'/guarantee' | langRoute" target="_blank">условиями гарантии</a> и
|
<a [routerLink]="'/guarantee' | langRoute" target="_blank">{{ 'cart.guaranteeTerms' | translate }}</a> {{ 'cart.and' | translate }}
|
||||||
<a [routerLink]="'/privacy-policy' | langRoute" target="_blank">политикой конфиденциальности</a>
|
<a [routerLink]="'/privacy-policy' | langRoute" target="_blank">{{ 'cart.privacyPolicy' | translate }}</a>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
[class.disabled]="!termsAccepted"
|
[class.disabled]="!termsAccepted"
|
||||||
[disabled]="!termsAccepted"
|
[disabled]="!termsAccepted"
|
||||||
>
|
>
|
||||||
Оформить заказ
|
{{ 'cart.checkout' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
@if (showPaymentPopup()) {
|
@if (showPaymentPopup()) {
|
||||||
<div class="payment-modal-overlay">
|
<div class="payment-modal-overlay">
|
||||||
<div class="payment-modal">
|
<div class="payment-modal">
|
||||||
<button class="close-modal-btn" (click)="closePaymentPopup()" aria-label="Закрыть">
|
<button class="close-modal-btn" (click)="closePaymentPopup()" [attr.aria-label]="'cart.close' | translate">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||||
<path d="M6 6L18 18M6 18L18 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
<path d="M6 6L18 18M6 18L18 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -147,14 +147,14 @@
|
|||||||
@if (paymentStatus() === 'creating') {
|
@if (paymentStatus() === 'creating') {
|
||||||
<div class="payment-status-screen">
|
<div class="payment-status-screen">
|
||||||
<div class="spinner-large"></div>
|
<div class="spinner-large"></div>
|
||||||
<h2>Создание платежа...</h2>
|
<h2>{{ 'cart.creatingPayment' | translate }}</h2>
|
||||||
<p>Подождите несколько секунд</p>
|
<p>{{ 'cart.waitFewSeconds' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (paymentStatus() === 'waiting') {
|
@if (paymentStatus() === 'waiting') {
|
||||||
<div class="payment-active">
|
<div class="payment-active">
|
||||||
<h2>Сканируйте QR-код для оплаты</h2>
|
<h2>{{ 'cart.scanQr' | translate }}</h2>
|
||||||
|
|
||||||
<div class="qr-section">
|
<div class="qr-section">
|
||||||
<div class="qr-wrapper">
|
<div class="qr-wrapper">
|
||||||
@@ -165,22 +165,22 @@
|
|||||||
|
|
||||||
<div class="payment-info">
|
<div class="payment-info">
|
||||||
<div class="payment-amount">
|
<div class="payment-amount">
|
||||||
<span class="label">Сумма к оплате:</span>
|
<span class="label">{{ 'cart.amountToPay' | translate }}</span>
|
||||||
<span class="amount">{{ totalPrice() | number:'1.2-2' }} RUB</span>
|
<span class="amount">{{ totalPrice() | number:'1.2-2' }} RUB</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="waiting-indicator">
|
<div class="waiting-indicator">
|
||||||
<div class="pulse-dot"></div>
|
<div class="pulse-dot"></div>
|
||||||
<span>Ожидание оплаты...</span>
|
<span>{{ 'cart.waitingPayment' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="payment-actions">
|
<div class="payment-actions">
|
||||||
<button class="copy-btn" (click)="copyPaymentLink()">
|
<button class="copy-btn" (click)="copyPaymentLink()">
|
||||||
{{ linkCopied() ? '✓ Скопировано' : 'Скопировать ссылку' }}
|
{{ linkCopied() ? ('cart.copied' | translate) : ('cart.copyLink' | translate) }}
|
||||||
</button>
|
</button>
|
||||||
<a [href]="paymentUrl()" target="_blank" rel="noopener noreferrer" class="open-btn">
|
<a [href]="paymentUrl()" target="_blank" rel="noopener noreferrer" class="open-btn">
|
||||||
Открыть в новой вкладке
|
{{ 'cart.openNewTab' | translate }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -189,8 +189,8 @@
|
|||||||
@if (paymentStatus() === 'success') {
|
@if (paymentStatus() === 'success') {
|
||||||
<div class="payment-status-screen success">
|
<div class="payment-status-screen success">
|
||||||
<div class="success-icon">✓</div>
|
<div class="success-icon">✓</div>
|
||||||
<h2>Поздравляем! Оплата прошла успешно!</h2>
|
<h2>{{ 'cart.paymentSuccess' | translate }}</h2>
|
||||||
<p class="success-text">Введите ваши контактные данные, и мы отправим вам покупку в течение нескольких минут</p>
|
<p class="success-text">{{ 'cart.paymentSuccessDesc' | translate }}</p>
|
||||||
|
|
||||||
<div class="email-form">
|
<div class="email-form">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -236,9 +236,9 @@
|
|||||||
>
|
>
|
||||||
@if (emailSubmitting()) {
|
@if (emailSubmitting()) {
|
||||||
<span class="spinner-small"></span>
|
<span class="spinner-small"></span>
|
||||||
Отправка...
|
{{ 'cart.sending' | translate }}
|
||||||
} @else {
|
} @else {
|
||||||
Отправить
|
{{ 'cart.send' | translate }}
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -248,9 +248,9 @@
|
|||||||
@if (paymentStatus() === 'timeout') {
|
@if (paymentStatus() === 'timeout') {
|
||||||
<div class="payment-status-screen timeout">
|
<div class="payment-status-screen timeout">
|
||||||
<div class="timeout-icon">⏱</div>
|
<div class="timeout-icon">⏱</div>
|
||||||
<h2>Время ожидания истекло</h2>
|
<h2>{{ 'cart.paymentTimeout' | translate }}</h2>
|
||||||
<p>Мы не получили подтверждение оплаты в течение 3 минут.</p>
|
<p>{{ 'cart.paymentTimeoutDesc' | translate }}</p>
|
||||||
<p class="auto-close">Окно закроется автоматически...</p>
|
<p class="auto-close">{{ 'cart.autoClose' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, computed, ChangeDetectionStrategy, signal, OnDestroy } from '@angular/core';
|
import { Component, computed, ChangeDetectionStrategy, signal, OnDestroy, inject } from '@angular/core';
|
||||||
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';
|
||||||
@@ -10,10 +10,12 @@ import { EmptyCartIconComponent } from '../../components/empty-cart-icon/empty-c
|
|||||||
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';
|
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
||||||
|
import { TranslatePipe } from '../../i18n/translate.pipe';
|
||||||
|
import { TranslateService } from '../../i18n/translate.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-cart',
|
selector: 'app-cart',
|
||||||
imports: [DecimalPipe, RouterLink, FormsModule, EmptyCartIconComponent, LangRoutePipe],
|
imports: [DecimalPipe, RouterLink, FormsModule, EmptyCartIconComponent, LangRoutePipe, TranslatePipe],
|
||||||
templateUrl: './cart.component.html',
|
templateUrl: './cart.component.html',
|
||||||
styleUrls: ['./cart.component.scss'],
|
styleUrls: ['./cart.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
@@ -25,6 +27,8 @@ export class CartComponent implements OnDestroy {
|
|||||||
termsAccepted = false;
|
termsAccepted = false;
|
||||||
isnovo = environment.theme === 'novo';
|
isnovo = environment.theme === 'novo';
|
||||||
|
|
||||||
|
private i18n = inject(TranslateService);
|
||||||
|
|
||||||
// Swipe state
|
// Swipe state
|
||||||
swipedItemId = signal<number | null>(null);
|
swipedItemId = signal<number | null>(null);
|
||||||
|
|
||||||
@@ -116,7 +120,7 @@ export class CartComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearCart(): void {
|
clearCart(): void {
|
||||||
if (confirm('Вы уверены, что хотите очистить корзину?')) {
|
if (confirm(this.i18n.t('cart.confirmClear'))) {
|
||||||
this.cartService.clearCart();
|
this.cartService.clearCart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +131,7 @@ export class CartComponent implements OnDestroy {
|
|||||||
|
|
||||||
checkout(): void {
|
checkout(): void {
|
||||||
if (!this.termsAccepted) {
|
if (!this.termsAccepted) {
|
||||||
alert('Пожалуйста, примите условия оферты, политику возврата и возврата для подтверждения оформления заказа.');
|
alert(this.i18n.t('cart.acceptTerms'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.openPaymentPopup();
|
this.openPaymentPopup();
|
||||||
@@ -249,7 +253,7 @@ export class CartComponent implements OnDestroy {
|
|||||||
this.linkCopied.set(true);
|
this.linkCopied.set(true);
|
||||||
setTimeout(() => this.linkCopied.set(false), 2000);
|
setTimeout(() => this.linkCopied.set(false), 2000);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('Ошибка копирования:', err);
|
console.error(this.i18n.t('cart.copyError'), err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,7 +318,7 @@ export class CartComponent implements OnDestroy {
|
|||||||
next: () => {
|
next: () => {
|
||||||
this.emailSubmitting.set(false);
|
this.emailSubmitting.set(false);
|
||||||
// Show success message
|
// Show success message
|
||||||
alert('Email успешно отправлен! Проверьте свою почту.');
|
alert(this.i18n.t('cart.emailSuccess'));
|
||||||
// Close popup and redirect to home page
|
// Close popup and redirect to home page
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.closePaymentPopup();
|
this.closePaymentPopup();
|
||||||
@@ -325,7 +329,7 @@ export class CartComponent implements OnDestroy {
|
|||||||
error: (err) => {
|
error: (err) => {
|
||||||
console.error('Error submitting email:', err);
|
console.error('Error submitting email:', err);
|
||||||
this.emailSubmitting.set(false);
|
this.emailSubmitting.set(false);
|
||||||
alert('Произошла ошибка при отправке email. Пожалуйста, попробуйте снова.');
|
alert(this.i18n.t('cart.emailError'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -386,11 +390,11 @@ export class CartComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (digitsOnly.length === 0) {
|
if (digitsOnly.length === 0) {
|
||||||
this.phoneError.set('Номер телефона обязателен');
|
this.phoneError.set(this.i18n.t('cart.phoneRequired'));
|
||||||
} else if (digitsOnly.length < 11) {
|
} else if (digitsOnly.length < 11) {
|
||||||
this.phoneError.set(`Введите ещё ${11 - digitsOnly.length} цифр`);
|
this.phoneError.set(this.i18n.t('cart.phoneMoreDigits', { count: 11 - digitsOnly.length }));
|
||||||
} else if (digitsOnly.length > 11) {
|
} else if (digitsOnly.length > 11) {
|
||||||
this.phoneError.set('Слишком много цифр');
|
this.phoneError.set(this.i18n.t('cart.phoneTooMany'));
|
||||||
} else {
|
} else {
|
||||||
this.phoneError.set('');
|
this.phoneError.set('');
|
||||||
}
|
}
|
||||||
@@ -418,19 +422,19 @@ export class CartComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (email.length === 0) {
|
if (email.length === 0) {
|
||||||
this.emailError.set('Email обязателен');
|
this.emailError.set(this.i18n.t('cart.emailRequired'));
|
||||||
} else if (email.length < 5) {
|
} else if (email.length < 5) {
|
||||||
this.emailError.set('Email слишком короткий (минимум 5 символов)');
|
this.emailError.set(this.i18n.t('cart.emailTooShort'));
|
||||||
} else if (email.length > 100) {
|
} else if (email.length > 100) {
|
||||||
this.emailError.set('Email слишком длинный (максимум 100 символов)');
|
this.emailError.set(this.i18n.t('cart.emailTooLong'));
|
||||||
} else if (!email.includes('@')) {
|
} else if (!email.includes('@')) {
|
||||||
this.emailError.set('Email должен содержать @');
|
this.emailError.set(this.i18n.t('cart.emailNeedsAt'));
|
||||||
} else if (!email.includes('.')) {
|
} else if (!email.includes('.')) {
|
||||||
this.emailError.set('Email должен содержать домен (.com, .ru и т.д.)');
|
this.emailError.set(this.i18n.t('cart.emailNeedsDomain'));
|
||||||
} else {
|
} else {
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
if (!emailRegex.test(email)) {
|
if (!emailRegex.test(email)) {
|
||||||
this.emailError.set('Некорректный формат email');
|
this.emailError.set(this.i18n.t('cart.emailInvalid'));
|
||||||
} else {
|
} else {
|
||||||
this.emailError.set('');
|
this.emailError.set('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@if (error()) {
|
@if (error()) {
|
||||||
<div class="error">
|
<div class="error">
|
||||||
<p>{{ error() }}</p>
|
<p>{{ error() }}</p>
|
||||||
<button (click)="resetAndLoad()">Попробовать снова</button>
|
<button (click)="resetAndLoad()">{{ 'category.retry' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<button class="add-to-cart-btn" (click)="addToCart(item.itemID, $event)">
|
<button class="add-to-cart-btn" (click)="addToCart(item.itemID, $event)">
|
||||||
В корзину
|
{{ 'category.addToCart' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -55,13 +55,13 @@
|
|||||||
@if (loading() && items().length > 0) {
|
@if (loading() && items().length > 0) {
|
||||||
<div class="loading-more">
|
<div class="loading-more">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Загрузка...</p>
|
<p>{{ 'category.loadingMore' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (!hasMore() && items().length > 0) {
|
@if (!hasMore() && items().length > 0) {
|
||||||
<div class="no-more">
|
<div class="no-more">
|
||||||
<p>Все товары загружены</p>
|
<p>{{ 'category.allLoaded' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,16 +75,16 @@
|
|||||||
<path d="M10 14H14" stroke="#d3dad9" stroke-width="1.5" stroke-linecap="round"/>
|
<path d="M10 14H14" stroke="#d3dad9" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3>Упс! Здесь пока пусто</h3>
|
<h3>{{ 'category.emptyTitle' | translate }}</h3>
|
||||||
<p>В этой категории ещё нет товаров, но скоро они появятся</p>
|
<p>{{ 'category.emptyDesc' | translate }}</p>
|
||||||
<a [routerLink]="'/' | langRoute" class="no-items-btn">На главную</a>
|
<a [routerLink]="'/' | langRoute" class="no-items-btn">{{ 'category.goHome' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (loading() && items().length === 0) {
|
@if (loading() && items().length === 0) {
|
||||||
<div class="loading-initial">
|
<div class="loading-initial">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Загрузка товаров...</p>
|
<p>{{ 'category.loading' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ 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';
|
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
||||||
|
import { TranslatePipe } from '../../i18n/translate.pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-category',
|
selector: 'app-category',
|
||||||
imports: [DecimalPipe, RouterLink, LangRoutePipe],
|
imports: [DecimalPipe, RouterLink, LangRoutePipe, TranslatePipe],
|
||||||
templateUrl: './category.component.html',
|
templateUrl: './category.component.html',
|
||||||
styleUrls: ['./category.component.scss'],
|
styleUrls: ['./category.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
<div class="loading">
|
<div class="loading">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Загрузка подкатегорий...</p>
|
<p>{{ 'subcategories.loading' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (error()) {
|
@if (error()) {
|
||||||
<div class="error">
|
<div class="error">
|
||||||
<p>{{ error() }}</p>
|
<p>{{ error() }}</p>
|
||||||
<button (click)="ngOnInit()">Попробовать снова</button>
|
<button (click)="ngOnInit()">{{ 'subcategories.retry' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,9 +45,9 @@
|
|||||||
<rect x="14" y="14" width="7" height="7" rx="1.5" stroke="#d3dad9" stroke-width="1.5"/>
|
<rect x="14" y="14" width="7" height="7" rx="1.5" stroke="#d3dad9" stroke-width="1.5"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3>Упс! Подкатегорий пока нет</h3>
|
<h3>{{ 'subcategories.emptyTitle' | translate }}</h3>
|
||||||
<p>В этом разделе ещё нет подкатегорий, но скоро они появятся</p>
|
<p>{{ 'subcategories.emptyDesc' | translate }}</p>
|
||||||
<a [routerLink]="'/' | langRoute" class="no-subcats-btn">На главную</a>
|
<a [routerLink]="'/' | langRoute" class="no-subcats-btn">{{ 'subcategories.goHome' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { Component, OnInit, OnDestroy, signal, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, OnInit, OnDestroy, signal, ChangeDetectionStrategy, inject } from '@angular/core';
|
||||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||||
import { ApiService, LanguageService } 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';
|
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
||||||
|
import { TranslatePipe } from '../../i18n/translate.pipe';
|
||||||
|
import { TranslateService } from '../../i18n/translate.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-subcategories',
|
selector: 'app-subcategories',
|
||||||
imports: [RouterLink, LangRoutePipe],
|
imports: [RouterLink, LangRoutePipe, TranslatePipe],
|
||||||
templateUrl: './subcategories.component.html',
|
templateUrl: './subcategories.component.html',
|
||||||
styleUrls: ['./subcategories.component.scss'],
|
styleUrls: ['./subcategories.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
@@ -17,6 +19,8 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
|||||||
subcategories = signal<Category[]>([]);
|
subcategories = signal<Category[]>([]);
|
||||||
loading = signal(true);
|
loading = signal(true);
|
||||||
error = signal<string | null>(null);
|
error = signal<string | null>(null);
|
||||||
|
|
||||||
|
private i18n = inject(TranslateService);
|
||||||
parentName = signal<string>('');
|
parentName = signal<string>('');
|
||||||
|
|
||||||
private routeSubscription?: Subscription;
|
private routeSubscription?: Subscription;
|
||||||
@@ -46,7 +50,7 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
|
|||||||
this.categories.set(cats);
|
this.categories.set(cats);
|
||||||
const subs = cats.filter(c => c.parentID === parentID);
|
const subs = cats.filter(c => c.parentID === parentID);
|
||||||
const parent = cats.find(c => c.categoryID === parentID);
|
const parent = cats.find(c => c.categoryID === parentID);
|
||||||
this.parentName.set(parent ? parent.name : 'Категория');
|
this.parentName.set(parent ? parent.name : this.i18n.t('home.categoriesTitle'));
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
<div class="novo-home">
|
<div class="novo-home">
|
||||||
<section class="novo-hero novo-hero-compact">
|
<section class="novo-hero novo-hero-compact">
|
||||||
<div class="novo-hero-content">
|
<div class="novo-hero-content">
|
||||||
<h1 class="novo-hero-title">Добро пожаловать в {{ brandName }}</h1>
|
<h1 class="novo-hero-title">{{ 'home.welcomeTo' | translate:{ brand: brandName } }}</h1>
|
||||||
<p class="novo-hero-subtitle">Найдите всё, что нужно, в одном месте</p>
|
<p class="novo-hero-subtitle">{{ 'home.subtitle' | translate }}</p>
|
||||||
<a [routerLink]="'/search' | langRoute" class="novo-hero-btn">
|
<a [routerLink]="'/search' | langRoute" class="novo-hero-btn">
|
||||||
Начать поиск
|
{{ 'home.startSearch' | translate }}
|
||||||
<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>
|
||||||
<polyline points="12 5 19 12 12 19"></polyline>
|
<polyline points="12 5 19 12 12 19"></polyline>
|
||||||
@@ -21,31 +21,31 @@
|
|||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
<div class="novo-loading">
|
<div class="novo-loading">
|
||||||
<div class="novo-spinner"></div>
|
<div class="novo-spinner"></div>
|
||||||
<p>Загружаем категории...</p>
|
<p>{{ 'home.loading' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (error()) {
|
@if (error()) {
|
||||||
<div class="novo-error">
|
<div class="novo-error">
|
||||||
<div class="novo-error-icon">⚠️</div>
|
<div class="novo-error-icon">⚠️</div>
|
||||||
<h3>Что-то пошло не так</h3>
|
<h3>{{ 'home.errorTitle' | translate }}</h3>
|
||||||
<p>{{ error() }}</p>
|
<p>{{ error() }}</p>
|
||||||
<button (click)="loadCategories()" class="novo-retry-btn">Попробовать снова</button>
|
<button (click)="loadCategories()" class="novo-retry-btn">{{ 'home.retry' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (!loading() && !error()) {
|
@if (!loading() && !error()) {
|
||||||
<section class="novo-categories">
|
<section class="novo-categories">
|
||||||
<div class="novo-section-header">
|
<div class="novo-section-header">
|
||||||
<h2>Категории товаров</h2>
|
<h2>{{ 'home.categoriesTitle' | translate }}</h2>
|
||||||
<p>Выберите интересующую категорию</p>
|
<p>{{ 'home.categoriesSubtitle' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (topLevelCategories().length === 0) {
|
@if (topLevelCategories().length === 0) {
|
||||||
<div class="novo-empty">
|
<div class="novo-empty">
|
||||||
<div class="novo-empty-icon">📦</div>
|
<div class="novo-empty-icon">📦</div>
|
||||||
<h3>Категории скоро появятся</h3>
|
<h3>{{ 'home.categoriesEmpty' | translate }}</h3>
|
||||||
<p>Мы работаем над наполнением каталога</p>
|
<p>{{ 'home.categoriesEmptyDesc' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="novo-categories-grid">
|
<div class="novo-categories-grid">
|
||||||
@@ -78,16 +78,16 @@
|
|||||||
<section class="dexar-hero">
|
<section class="dexar-hero">
|
||||||
<div class="dexar-hero-overlay">
|
<div class="dexar-hero-overlay">
|
||||||
<div class="dexar-hero-content">
|
<div class="dexar-hero-content">
|
||||||
<h1 class="dexar-hero-title">Здесь ты найдёшь всё</h1>
|
<h1 class="dexar-hero-title">{{ 'home.dexarHeroTitle' | translate }}</h1>
|
||||||
<p class="dexar-hero-subtitle">Тысячи товаров в одном месте</p>
|
<p class="dexar-hero-subtitle">{{ 'home.dexarHeroSubtitle' | translate }}</p>
|
||||||
<p class="dexar-hero-tagline">просто и удобно</p>
|
<p class="dexar-hero-tagline">{{ 'home.dexarHeroTagline' | translate }}</p>
|
||||||
|
|
||||||
<div class="dexar-hero-actions">
|
<div class="dexar-hero-actions">
|
||||||
<a (click)="scrollToCatalog()" class="dexar-btn-primary">
|
<a (click)="scrollToCatalog()" class="dexar-btn-primary">
|
||||||
Перейти в каталог
|
{{ 'home.goToCatalog' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<button (click)="navigateToSearch()" class="dexar-btn-secondary">
|
<button (click)="navigateToSearch()" class="dexar-btn-secondary">
|
||||||
Найти товар
|
{{ 'home.findProduct' | translate }}
|
||||||
<svg width="11" height="16" viewBox="0 0 11 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="11" height="16" viewBox="0 0 11 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M1 1L9 8L1 15" stroke="#1E3C38" stroke-width="2"/>
|
<path d="M1 1L9 8L1 15" stroke="#1E3C38" stroke-width="2"/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -103,25 +103,25 @@
|
|||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
<div class="dexar-loading">
|
<div class="dexar-loading">
|
||||||
<div class="dexar-spinner"></div>
|
<div class="dexar-spinner"></div>
|
||||||
<p>Загрузка категорий...</p>
|
<p>{{ 'home.loadingDexar' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (error()) {
|
@if (error()) {
|
||||||
<div class="dexar-error">
|
<div class="dexar-error">
|
||||||
<p>{{ error() }}</p>
|
<p>{{ error() }}</p>
|
||||||
<button (click)="loadCategories()" class="dexar-retry-btn">Попробовать снова</button>
|
<button (click)="loadCategories()" class="dexar-retry-btn">{{ 'home.retry' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (!loading() && !error()) {
|
@if (!loading() && !error()) {
|
||||||
<section class="dexar-categories" id="catalog">
|
<section class="dexar-categories" id="catalog">
|
||||||
<h2 class="dexar-categories-title">Каталог товаров</h2>
|
<h2 class="dexar-categories-title">{{ 'home.catalogTitle' | translate }}</h2>
|
||||||
@if (topLevelCategories().length === 0) {
|
@if (topLevelCategories().length === 0) {
|
||||||
<div class="dexar-empty-categories">
|
<div class="dexar-empty-categories">
|
||||||
<div class="dexar-empty-icon">📦</div>
|
<div class="dexar-empty-icon">📦</div>
|
||||||
<h3>Категории пока отсутствуют</h3>
|
<h3>{{ 'home.emptyCategoriesDexar' | translate }}</h3>
|
||||||
<p>Скоро здесь появятся категории товаров</p>
|
<p>{{ 'home.categoriesSoonDexar' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="dexar-categories-grid">
|
<div class="dexar-categories-grid">
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="dexar-category-info">
|
<div class="dexar-category-info">
|
||||||
<h3 class="dexar-category-name">{{ category.name }}</h3>
|
<h3 class="dexar-category-name">{{ category.name }}</h3>
|
||||||
<p class="dexar-category-count">{{ getItemCount(category.categoryID) }} товаров</p>
|
<p class="dexar-category-count">{{ 'home.itemsCount' | translate:{ count: getItemCount(category.categoryID) } }}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ 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';
|
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
||||||
|
import { TranslatePipe } from '../../i18n/translate.pipe';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
imports: [RouterLink, ItemsCarouselComponent, LangRoutePipe],
|
imports: [RouterLink, ItemsCarouselComponent, LangRoutePipe, TranslatePipe],
|
||||||
templateUrl: './home.component.html',
|
templateUrl: './home.component.html',
|
||||||
styleUrls: ['./home.component.scss'],
|
styleUrls: ['./home.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
<div class="novo-loading">
|
<div class="novo-loading">
|
||||||
<div class="novo-spinner"></div>
|
<div class="novo-spinner"></div>
|
||||||
<p>Загрузка...</p>
|
<p>{{ 'itemDetail.loading' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (error()) {
|
@if (error()) {
|
||||||
<div class="novo-error">
|
<div class="novo-error">
|
||||||
<p>{{ error() }}</p>
|
<p>{{ error() }}</p>
|
||||||
<a [routerLink]="'/' | langRoute" class="novo-back-link">Вернуться</a>
|
<a [routerLink]="'/' | langRoute" class="novo-back-link">{{ 'itemDetail.back' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
||||||
<path d="M21 15l-5-5L5 21"></path>
|
<path d="M21 15l-5-5L5 21"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<p>Нет изображения</p>
|
<p>{{ 'itemDetail.noImage' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -76,10 +76,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="novo-stock">
|
<div class="novo-stock">
|
||||||
<span class="stock-label">Наличие:</span>
|
<span class="stock-label">{{ 'itemDetail.stock' | translate }}</span>
|
||||||
<div class="stock-indicator" [class.high]="item()!.remainings === 'high'" [class.medium]="item()!.remainings === 'medium'" [class.low]="item()!.remainings === 'low'">
|
<div class="stock-indicator" [class.high]="item()!.remainings === 'high'" [class.medium]="item()!.remainings === 'medium'" [class.low]="item()!.remainings === 'low'">
|
||||||
<span class="dot"></span>
|
<span class="dot"></span>
|
||||||
{{ item()!.remainings === 'high' ? 'В наличии' : item()!.remainings === 'medium' ? 'Мало' : 'Осталось немного' }}
|
{{ item()!.remainings === 'high' ? ('itemDetail.inStock' | translate) : item()!.remainings === 'medium' ? ('itemDetail.mediumStock' | translate) : ('itemDetail.lowStock' | translate) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -89,24 +89,24 @@
|
|||||||
<circle cx="20" cy="21" r="1"></circle>
|
<circle cx="20" cy="21" r="1"></circle>
|
||||||
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
|
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
|
||||||
</svg>
|
</svg>
|
||||||
Добавить в корзину
|
{{ 'itemDetail.addToCart' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="novo-description">
|
<div class="novo-description">
|
||||||
<h3>Описание</h3>
|
<h3>{{ 'itemDetail.description' | translate }}</h3>
|
||||||
<div [innerHTML]="getSafeHtml(item()!.description)"></div>
|
<div [innerHTML]="getSafeHtml(item()!.description)"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="novo-reviews">
|
<div class="novo-reviews">
|
||||||
<h2>Отзывы ({{ item()!.callbacks?.length || 0 }})</h2>
|
<h2>{{ 'itemDetail.reviews' | translate }} ({{ item()!.callbacks?.length || 0 }})</h2>
|
||||||
|
|
||||||
<!-- novo Review Form -->
|
<!-- novo Review Form -->
|
||||||
<div class="novo-review-form">
|
<div class="novo-review-form">
|
||||||
<h3>Ваш отзыв</h3>
|
<h3>{{ 'itemDetail.yourReview' | translate }}</h3>
|
||||||
<div class="novo-rating-input">
|
<div class="novo-rating-input">
|
||||||
<label>Оценка:</label>
|
<label>{{ 'itemDetail.rating' | translate }}</label>
|
||||||
<div class="novo-star-selector">
|
<div class="novo-star-selector">
|
||||||
@for (star of [1, 2, 3, 4, 5]; track star) {
|
@for (star of [1, 2, 3, 4, 5]; track star) {
|
||||||
<span
|
<span
|
||||||
@@ -120,14 +120,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
[(ngModel)]="newReview.comment"
|
[(ngModel)]="newReview.comment"
|
||||||
placeholder="Поделитесь своими впечатлениями о товаре..."
|
[placeholder]="'itemDetail.reviewPlaceholder' | translate"
|
||||||
rows="4"
|
rows="4"
|
||||||
class="novo-textarea">
|
class="novo-textarea">
|
||||||
</textarea>
|
</textarea>
|
||||||
<div class="novo-form-actions">
|
<div class="novo-form-actions">
|
||||||
<label class="novo-anonymous-toggle">
|
<label class="novo-anonymous-toggle">
|
||||||
<input type="checkbox" [(ngModel)]="newReview.anonymous">
|
<input type="checkbox" [(ngModel)]="newReview.anonymous">
|
||||||
<span>Анонимно</span>
|
<span>{{ 'itemDetail.anonymous' | translate }}</span>
|
||||||
</label>
|
</label>
|
||||||
@if (!newReview.anonymous && getUserDisplayName()) {
|
@if (!newReview.anonymous && getUserDisplayName()) {
|
||||||
<span class="novo-username-preview">{{ getUserDisplayName() }}</span>
|
<span class="novo-username-preview">{{ getUserDisplayName() }}</span>
|
||||||
@@ -139,12 +139,12 @@
|
|||||||
[class.submitting]="reviewSubmitStatus() === 'loading'">
|
[class.submitting]="reviewSubmitStatus() === 'loading'">
|
||||||
@if (reviewSubmitStatus() === 'loading') {
|
@if (reviewSubmitStatus() === 'loading') {
|
||||||
<span class="novo-spinner-small"></span>
|
<span class="novo-spinner-small"></span>
|
||||||
Отправка...
|
{{ 'itemDetail.submitting' | translate }}
|
||||||
} @else {
|
} @else {
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
|
<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
|
||||||
</svg>
|
</svg>
|
||||||
Отправить
|
{{ 'itemDetail.submit' | translate }}
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
<path d="M20 6L9 17l-5-5"/>
|
<path d="M20 6L9 17l-5-5"/>
|
||||||
</svg>
|
</svg>
|
||||||
Спасибо за ваш отзыв!
|
{{ 'itemDetail.reviewSuccess' | translate }}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||||||
<path d="M18 6L6 18M6 6l12 12"/>
|
<path d="M18 6L6 18M6 6l12 12"/>
|
||||||
</svg>
|
</svg>
|
||||||
Ошибка отправки. Попробуйте позже.
|
{{ 'itemDetail.reviewError' | translate }}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
<div class="novo-review-card">
|
<div class="novo-review-card">
|
||||||
<div class="review-header">
|
<div class="review-header">
|
||||||
<div class="reviewer-info">
|
<div class="reviewer-info">
|
||||||
<span class="reviewer-name">{{ review.userID || 'Пользователь' }}</span>
|
<span class="reviewer-name">{{ review.userID ? review.userID : ('itemDetail.defaultUser' | translate) }}</span>
|
||||||
@if (review.timestamp) {
|
@if (review.timestamp) {
|
||||||
<span class="review-date">{{ formatDate(review.timestamp) }}</span>
|
<span class="review-date">{{ formatDate(review.timestamp) }}</span>
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
<p class="novo-no-reviews">Пока нет отзывов. Станьте первым!</p>
|
<p class="novo-no-reviews">{{ 'itemDetail.noReviews' | translate }}</p>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,14 +197,14 @@
|
|||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
<div class="dx-loading">
|
<div class="dx-loading">
|
||||||
<div class="dx-spinner"></div>
|
<div class="dx-spinner"></div>
|
||||||
<p>Загрузка товара...</p>
|
<p>{{ 'itemDetail.loadingDexar' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (error()) {
|
@if (error()) {
|
||||||
<div class="dx-error">
|
<div class="dx-error">
|
||||||
<p>{{ error() }}</p>
|
<p>{{ error() }}</p>
|
||||||
<a [routerLink]="'/' | langRoute" class="dx-back-link">Вернуться на главную</a>
|
<a [routerLink]="'/' | langRoute" class="dx-back-link">{{ 'itemDetail.backHome' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@
|
|||||||
@if (photo.video) {
|
@if (photo.video) {
|
||||||
<div class="dx-video-badge">▶</div>
|
<div class="dx-video-badge">▶</div>
|
||||||
}
|
}
|
||||||
<img [src]="photo.url" [alt]="'Фото ' + ($index + 1)" loading="lazy" decoding="async" />
|
<img [src]="photo.url" [alt]="('itemDetail.photo' | translate) + ' ' + ($index + 1)" loading="lazy" decoding="async" />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
||||||
<path d="M21 15l-5-5L5 21"></path>
|
<path d="M21 15l-5-5L5 21"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Нет изображения</span>
|
<span>{{ 'itemDetail.noImage' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -260,7 +260,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<span class="dx-rating-value">{{ item()!.rating }}</span>
|
<span class="dx-rating-value">{{ item()!.rating }}</span>
|
||||||
<span class="dx-rating-count">({{ item()!.callbacks?.length || 0 }} отзывов)</span>
|
<span class="dx-rating-count">({{ item()!.callbacks?.length || 0 }} {{ 'itemDetail.reviewsCount' | translate }})</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dx-price-block">
|
<div class="dx-price-block">
|
||||||
@@ -276,13 +276,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dx-stock">
|
<div class="dx-stock">
|
||||||
<span class="dx-stock-label">Наличие:</span>
|
<span class="dx-stock-label">{{ 'itemDetail.stock' | translate }}</span>
|
||||||
<span class="dx-stock-status"
|
<span class="dx-stock-status"
|
||||||
[class.high]="item()!.remainings === 'high'"
|
[class.high]="item()!.remainings === 'high'"
|
||||||
[class.medium]="item()!.remainings === 'medium'"
|
[class.medium]="item()!.remainings === 'medium'"
|
||||||
[class.low]="item()!.remainings === 'low'">
|
[class.low]="item()!.remainings === 'low'">
|
||||||
<span class="dx-stock-dot"></span>
|
<span class="dx-stock-dot"></span>
|
||||||
{{ item()!.remainings === 'high' ? 'В наличии' : item()!.remainings === 'medium' ? 'Заканчивается' : 'Последние штуки' }}
|
{{ item()!.remainings === 'high' ? ('itemDetail.inStock' | translate) : item()!.remainings === 'medium' ? ('itemDetail.mediumStock' | translate) : ('itemDetail.lastItems' | translate) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -292,11 +292,11 @@
|
|||||||
<circle cx="20" cy="21" r="1"></circle>
|
<circle cx="20" cy="21" r="1"></circle>
|
||||||
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
|
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
|
||||||
</svg>
|
</svg>
|
||||||
Добавить в корзину
|
{{ 'itemDetail.addToCart' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="dx-description">
|
<div class="dx-description">
|
||||||
<h2>Описание</h2>
|
<h2>{{ 'itemDetail.description' | translate }}</h2>
|
||||||
<div class="dx-description-text" [innerHTML]="getSafeHtml(item()!.description)"></div>
|
<div class="dx-description-text" [innerHTML]="getSafeHtml(item()!.description)"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -304,12 +304,12 @@
|
|||||||
|
|
||||||
<!-- Reviews Section -->
|
<!-- Reviews Section -->
|
||||||
<div class="dx-reviews-section">
|
<div class="dx-reviews-section">
|
||||||
<h2>Отзывы ({{ item()!.callbacks?.length || 0 }})</h2>
|
<h2>{{ 'itemDetail.reviews' | translate }} ({{ item()!.callbacks?.length || 0 }})</h2>
|
||||||
|
|
||||||
<div class="dx-review-form">
|
<div class="dx-review-form">
|
||||||
<h3>Оставить отзыв</h3>
|
<h3>{{ 'itemDetail.leaveReview' | translate }}</h3>
|
||||||
<div class="dx-rating-input">
|
<div class="dx-rating-input">
|
||||||
<label>Оценка:</label>
|
<label>{{ 'itemDetail.rating' | translate }}</label>
|
||||||
<div class="dx-star-selector">
|
<div class="dx-star-selector">
|
||||||
@for (star of [1, 2, 3, 4, 5]; track star) {
|
@for (star of [1, 2, 3, 4, 5]; track star) {
|
||||||
<span
|
<span
|
||||||
@@ -323,14 +323,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
[(ngModel)]="newReview.comment"
|
[(ngModel)]="newReview.comment"
|
||||||
placeholder="Поделитесь впечатлениями о товаре..."
|
[placeholder]="'itemDetail.reviewPlaceholderDexar' | translate"
|
||||||
rows="4"
|
rows="4"
|
||||||
class="dx-textarea">
|
class="dx-textarea">
|
||||||
</textarea>
|
</textarea>
|
||||||
<div class="dx-form-actions">
|
<div class="dx-form-actions">
|
||||||
<label class="dx-anon-toggle">
|
<label class="dx-anon-toggle">
|
||||||
<input type="checkbox" [(ngModel)]="newReview.anonymous">
|
<input type="checkbox" [(ngModel)]="newReview.anonymous">
|
||||||
<span>Анонимно</span>
|
<span>{{ 'itemDetail.anonymous' | translate }}</span>
|
||||||
</label>
|
</label>
|
||||||
@if (!newReview.anonymous && getUserDisplayName()) {
|
@if (!newReview.anonymous && getUserDisplayName()) {
|
||||||
<span class="dx-user-preview">{{ getUserDisplayName() }}</span>
|
<span class="dx-user-preview">{{ getUserDisplayName() }}</span>
|
||||||
@@ -342,9 +342,9 @@
|
|||||||
[class.submitting]="reviewSubmitStatus() === 'loading'">
|
[class.submitting]="reviewSubmitStatus() === 'loading'">
|
||||||
@if (reviewSubmitStatus() === 'loading') {
|
@if (reviewSubmitStatus() === 'loading') {
|
||||||
<span class="dx-spinner-sm"></span>
|
<span class="dx-spinner-sm"></span>
|
||||||
Отправка...
|
{{ 'itemDetail.submitting' | translate }}
|
||||||
} @else {
|
} @else {
|
||||||
Отправить
|
{{ 'itemDetail.submit' | translate }}
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -352,14 +352,14 @@
|
|||||||
@if (reviewSubmitStatus() === 'success') {
|
@if (reviewSubmitStatus() === 'success') {
|
||||||
<div class="dx-status success">
|
<div class="dx-status success">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M20 6L9 17l-5-5"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M20 6L9 17l-5-5"/></svg>
|
||||||
Спасибо за ваш отзыв!
|
{{ 'itemDetail.reviewSuccess' | translate }}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (reviewSubmitStatus() === 'error') {
|
@if (reviewSubmitStatus() === 'error') {
|
||||||
<div class="dx-status error">
|
<div class="dx-status error">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
||||||
Ошибка отправки. Попробуйте позже.
|
{{ 'itemDetail.reviewError' | translate }}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -370,7 +370,7 @@
|
|||||||
<div class="dx-review-card">
|
<div class="dx-review-card">
|
||||||
<div class="dx-review-header">
|
<div class="dx-review-header">
|
||||||
<div class="dx-reviewer">
|
<div class="dx-reviewer">
|
||||||
<span class="dx-reviewer-name">{{ callback.userID || 'Аноним' }}</span>
|
<span class="dx-reviewer-name">{{ callback.userID ? callback.userID : ('itemDetail.defaultUser' | translate) }}</span>
|
||||||
@if (callback.timestamp) {
|
@if (callback.timestamp) {
|
||||||
<span class="dx-review-date">{{ formatDate(callback.timestamp) }}</span>
|
<span class="dx-review-date">{{ formatDate(callback.timestamp) }}</span>
|
||||||
}
|
}
|
||||||
@@ -391,7 +391,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
<p class="dx-no-reviews">Пока нет отзывов. Станьте первым!</p>
|
<p class="dx-no-reviews">{{ 'itemDetail.noReviews' | translate }}</p>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -399,7 +399,7 @@
|
|||||||
<!-- Q&A Section -->
|
<!-- Q&A Section -->
|
||||||
@if (item()!.questions && item()!.questions!.length > 0) {
|
@if (item()!.questions && item()!.questions!.length > 0) {
|
||||||
<div class="dx-qa-section">
|
<div class="dx-qa-section">
|
||||||
<h2>Вопросы и ответы ({{ item()!.questions!.length }})</h2>
|
<h2>{{ 'itemDetail.qna' | translate }} ({{ item()!.questions!.length }})</h2>
|
||||||
<div class="dx-qa-list">
|
<div class="dx-qa-list">
|
||||||
@for (question of item()!.questions!; track $index) {
|
@for (question of item()!.questions!; track $index) {
|
||||||
<div class="dx-qa-card">
|
<div class="dx-qa-card">
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ 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';
|
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
||||||
|
import { TranslatePipe } from '../../i18n/translate.pipe';
|
||||||
|
import { TranslateService } from '../../i18n/translate.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-item-detail',
|
selector: 'app-item-detail',
|
||||||
imports: [DecimalPipe, RouterLink, FormsModule, LangRoutePipe],
|
imports: [DecimalPipe, RouterLink, FormsModule, LangRoutePipe, TranslatePipe],
|
||||||
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
|
||||||
@@ -39,6 +41,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
|
|||||||
private reloadTimeout?: ReturnType<typeof setTimeout>;
|
private reloadTimeout?: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
private seoService = inject(SeoService);
|
private seoService = inject(SeoService);
|
||||||
|
private i18n = inject(TranslateService);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@@ -115,10 +118,10 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
|
|||||||
const diffMs = now.getTime() - date.getTime();
|
const diffMs = now.getTime() - date.getTime();
|
||||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
if (diffDays === 0) return 'Сегодня';
|
if (diffDays === 0) return this.i18n.t('itemDetail.today');
|
||||||
if (diffDays === 1) return 'Вчера';
|
if (diffDays === 1) return this.i18n.t('itemDetail.yesterday');
|
||||||
if (diffDays < 7) return `${diffDays} дн. назад`;
|
if (diffDays < 7) return `${diffDays} ${this.i18n.t('itemDetail.daysAgo')}`;
|
||||||
if (diffDays < 30) return `${Math.floor(diffDays / 7)} нед. назад`;
|
if (diffDays < 30) return `${Math.floor(diffDays / 7)} ${this.i18n.t('itemDetail.weeksAgo')}`;
|
||||||
|
|
||||||
return date.toLocaleDateString('ru-RU', {
|
return date.toLocaleDateString('ru-RU', {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
@@ -133,7 +136,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
getUserDisplayName(): string | null {
|
getUserDisplayName(): string | null {
|
||||||
if (!this.telegramService.isTelegramApp()) {
|
if (!this.telegramService.isTelegramApp()) {
|
||||||
return 'Пользователь';
|
return this.i18n.t('itemDetail.defaultUser');
|
||||||
}
|
}
|
||||||
return this.telegramService.getDisplayName();
|
return this.telegramService.getDisplayName();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<div class="search-header">
|
<div class="search-header">
|
||||||
<h1>Поиск товаров</h1>
|
<h1>{{ 'search.title' | translate }}</h1>
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
[(ngModel)]="searchQuery"
|
[(ngModel)]="searchQuery"
|
||||||
(input)="onSearchInput(searchQuery)"
|
(input)="onSearchInput(searchQuery)"
|
||||||
placeholder="Введите название товара..."
|
[placeholder]="'search.placeholder' | translate"
|
||||||
class="search-input"
|
class="search-input"
|
||||||
autofocus
|
autofocus
|
||||||
/>
|
/>
|
||||||
@@ -21,21 +21,21 @@
|
|||||||
|
|
||||||
@if (searchQuery && items().length > 0) {
|
@if (searchQuery && items().length > 0) {
|
||||||
<div class="results-count">
|
<div class="results-count">
|
||||||
Найдено товаров: {{ items().length }}@if (totalResults() > items().length) { из {{ totalResults() }} }
|
{{ 'search.resultsCount' | translate }} {{ items().length }}@if (totalResults() > items().length) { {{ 'search.of' | translate }} {{ totalResults() }} }
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (loading() && items().length === 0) {
|
@if (loading() && items().length === 0) {
|
||||||
<div class="loading-initial">
|
<div class="loading-initial">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Поиск...</p>
|
<p>{{ 'search.searching' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (error()) {
|
@if (error()) {
|
||||||
<div class="error">
|
<div class="error">
|
||||||
<p>{{ error() }}</p>
|
<p>{{ error() }}</p>
|
||||||
<button (click)="performSearch(searchQuery)">Попробовать снова</button>
|
<button (click)="performSearch(searchQuery)">{{ 'search.retry' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,9 +47,9 @@
|
|||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2929 18.2929C18.6834 17.9024 19.3166 17.9024 19.7071 18.2929L25.7071 24.2929C26.0976 24.6834 26.0976 25.3166 25.7071 25.7071C25.3166 26.0976 24.6834 26.0976 24.2929 25.7071L18.2929 19.7071C17.9024 19.3166 17.9024 18.6834 18.2929 18.2929Z" fill="#a1b4b5" />
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2929 18.2929C18.6834 17.9024 19.3166 17.9024 19.7071 18.2929L25.7071 24.2929C26.0976 24.6834 26.0976 25.3166 25.7071 25.7071C25.3166 26.0976 24.6834 26.0976 24.2929 25.7071L18.2929 19.7071C17.9024 19.3166 17.9024 18.6834 18.2929 18.2929Z" fill="#a1b4b5" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h2>Ничего не найдено</h2>
|
<h2>{{ 'search.noResults' | translate }}</h2>
|
||||||
<p>По запросу "{{ searchQuery }}" товары не найдены</p>
|
<p>{{ 'search.noResultsFor' | translate:{ query: searchQuery } }}</p>
|
||||||
<p class="hint">Попробуйте изменить запрос или используйте другие ключевые слова</p>
|
<p class="hint">{{ 'search.noResultsHint' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<button class="add-to-cart-btn" (click)="addToCart(item.itemID, $event)">
|
<button class="add-to-cart-btn" (click)="addToCart(item.itemID, $event)">
|
||||||
В корзину
|
{{ 'search.addToCart' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -104,13 +104,13 @@
|
|||||||
@if (loading() && items().length > 0) {
|
@if (loading() && items().length > 0) {
|
||||||
<div class="loading-more">
|
<div class="loading-more">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Загрузка...</p>
|
<p>{{ 'search.loadingMore' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (!hasMore() && items().length > 0) {
|
@if (!hasMore() && items().length > 0) {
|
||||||
<div class="no-more">
|
<div class="no-more">
|
||||||
<p>Все результаты загружены</p>
|
<p>{{ 'search.allLoaded' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2929 18.2929C18.6834 17.9024 19.3166 17.9024 19.7071 18.2929L25.7071 24.2929C26.0976 24.6834 26.0976 25.3166 25.7071 25.7071C25.3166 26.0976 24.6834 26.0976 24.2929 25.7071L18.2929 19.7071C17.9024 19.3166 17.9024 18.6834 18.2929 18.2929Z" fill="#d3dad9" />
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2929 18.2929C18.6834 17.9024 19.3166 17.9024 19.7071 18.2929L25.7071 24.2929C26.0976 24.6834 26.0976 25.3166 25.7071 25.7071C25.3166 26.0976 24.6834 26.0976 24.2929 25.7071L18.2929 19.7071C17.9024 19.3166 17.9024 18.6834 18.2929 18.2929Z" fill="#d3dad9" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<p>Введите запрос для поиска товаров</p>
|
<p>{{ 'search.emptyState' | translate }}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, signal, HostListener, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, signal, HostListener, OnDestroy, ChangeDetectionStrategy, inject } from '@angular/core';
|
||||||
import { DecimalPipe } from '@angular/common';
|
import { DecimalPipe } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
@@ -8,10 +8,12 @@ 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';
|
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
|
||||||
|
import { TranslatePipe } from '../../i18n/translate.pipe';
|
||||||
|
import { TranslateService } from '../../i18n/translate.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-search',
|
selector: 'app-search',
|
||||||
imports: [DecimalPipe, FormsModule, RouterLink, LangRoutePipe],
|
imports: [DecimalPipe, FormsModule, RouterLink, LangRoutePipe, TranslatePipe],
|
||||||
templateUrl: './search.component.html',
|
templateUrl: './search.component.html',
|
||||||
styleUrls: ['./search.component.scss'],
|
styleUrls: ['./search.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
@@ -29,6 +31,7 @@ export class SearchComponent implements OnDestroy {
|
|||||||
private isLoadingMore = false;
|
private isLoadingMore = false;
|
||||||
private searchSubject = new Subject<string>();
|
private searchSubject = new Subject<string>();
|
||||||
private searchSubscription: Subscription;
|
private searchSubscription: Subscription;
|
||||||
|
private i18n = inject(TranslateService);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
@@ -100,7 +103,7 @@ export class SearchComponent implements OnDestroy {
|
|||||||
this.isLoadingMore = false;
|
this.isLoadingMore = false;
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
this.error.set('Ошибка при поиске товаров');
|
this.error.set(this.i18n.t('home.errorTitle'));
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
this.isLoadingMore = false;
|
this.isLoadingMore = false;
|
||||||
console.error('Error searching items:', err);
|
console.error('Error searching items:', err);
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ export class SeoService {
|
|||||||
* Reset meta tags back to defaults (call on navigation away from item page).
|
* Reset meta tags back to defaults (call on navigation away from item page).
|
||||||
*/
|
*/
|
||||||
resetToDefaults(): void {
|
resetToDefaults(): void {
|
||||||
const defaultTitle = `${this.siteName} — Маркетплейс товаров и услуг`;
|
const defaultTitle = `${this.siteName} — Marketplace`;
|
||||||
const defaultDescription = 'Современный маркетплейс для покупки цифровых товаров. Широкий выбор товаров, удобный поиск, быстрая доставка.';
|
const defaultDescription = 'Modern marketplace for buying digital goods. Wide selection, convenient search, fast delivery.';
|
||||||
const defaultImage = `${this.siteUrl}/og-image.jpg`;
|
const defaultImage = `${this.siteUrl}/og-image.jpg`;
|
||||||
|
|
||||||
this.title.setTitle(defaultTitle);
|
this.title.setTitle(defaultTitle);
|
||||||
|
|||||||
@@ -34,6 +34,6 @@ export class TelegramService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getDisplayName(): string {
|
getDisplayName(): string {
|
||||||
return this.getUsername() || this.getFullName() || 'Пользователь';
|
return this.getUsername() || this.getFullName() || 'User';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user