added translations

This commit is contained in:
sdarbinyan
2026-02-26 23:09:20 +04:00
parent e4206d8abc
commit caf14eeae1
29 changed files with 1038 additions and 202 deletions

View File

@@ -1,12 +1,12 @@
<div [class]="isnovo ? 'cart-container novo' : 'cart-container dexar'">
<div class="cart-header">
<h1>Корзина</h1>
<h1>{{ 'cart.title' | translate }}</h1>
@if (itemCount() > 0) {
<button class="clear-cart-btn" (click)="clearCart()">
<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"/>
</svg>
Очистить
{{ 'cart.clear' | translate }}
</button>
}
</div>
@@ -16,9 +16,9 @@
<div class="empty-icon">
<app-empty-cart-icon />
</div>
<h2>Корзина пуста</h2>
<p>Добавьте товары, чтобы начать покупки</p>
<a [routerLink]="'/' | langRoute" class="shop-btn">Перейти к покупкам</a>
<h2>{{ 'cart.empty' | translate }}</h2>
<p>{{ 'cart.emptyDesc' | translate }}</p>
<a [routerLink]="'/' | langRoute" class="shop-btn">{{ 'cart.goShopping' | translate }}</a>
</div>
}
@@ -86,21 +86,21 @@
<div class="cart-summary">
<div class="summary-header">
<h3>Итого</h3>
<h3>{{ 'cart.total' | translate }}</h3>
</div>
<div class="summary-row">
<span>Товары ({{ itemCount() }})</span>
<span>{{ 'cart.items' | translate }} ({{ itemCount() }})</span>
<span class="value">{{ totalPrice() | number:'1.2-2' }} ₽</span>
</div>
<div class="summary-row delivery">
<span>Доставка</span>
<span>{{ 'cart.deliveryLabel' | translate }}</span>
<span>0 ₽</span>
</div>
<div class="summary-row total">
<span>К оплате</span>
<span>{{ 'cart.toPay' | translate }}</span>
<span class="total-price">{{ totalPrice() | number:'1.2-2' }} ₽</span>
</div>
@@ -113,11 +113,11 @@
/>
<span class="checkmark"></span>
<span class="terms-text">
Я согласен с
<a [routerLink]="'/public-offer' | langRoute" target="_blank">публичной офертой</a>,
<a [routerLink]="'/return-policy' | langRoute" target="_blank">политикой возврата</a>,
<a [routerLink]="'/guarantee' | langRoute" target="_blank">условиями гарантии</a> и
<a [routerLink]="'/privacy-policy' | langRoute" target="_blank">политикой конфиденциальности</a>
{{ 'cart.agreeWith' | translate }}
<a [routerLink]="'/public-offer' | langRoute" target="_blank">{{ 'cart.publicOffer' | translate }}</a>,
<a [routerLink]="'/return-policy' | langRoute" target="_blank">{{ 'cart.returnPolicy' | translate }}</a>,
<a [routerLink]="'/guarantee' | langRoute" target="_blank">{{ 'cart.guaranteeTerms' | translate }}</a> {{ 'cart.and' | translate }}
<a [routerLink]="'/privacy-policy' | langRoute" target="_blank">{{ 'cart.privacyPolicy' | translate }}</a>
</span>
</label>
</div>
@@ -128,7 +128,7 @@
[class.disabled]="!termsAccepted"
[disabled]="!termsAccepted"
>
Оформить заказ
{{ 'cart.checkout' | translate }}
</button>
</div>
</div>
@@ -139,7 +139,7 @@
@if (showPaymentPopup()) {
<div class="payment-modal-overlay">
<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">
<path d="M6 6L18 18M6 18L18 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
@@ -147,14 +147,14 @@
@if (paymentStatus() === 'creating') {
<div class="payment-status-screen">
<div class="spinner-large"></div>
<h2>Создание платежа...</h2>
<p>Подождите несколько секунд</p>
<h2>{{ 'cart.creatingPayment' | translate }}</h2>
<p>{{ 'cart.waitFewSeconds' | translate }}</p>
</div>
}
@if (paymentStatus() === 'waiting') {
<div class="payment-active">
<h2>Сканируйте QR-код для оплаты</h2>
<h2>{{ 'cart.scanQr' | translate }}</h2>
<div class="qr-section">
<div class="qr-wrapper">
@@ -165,22 +165,22 @@
<div class="payment-info">
<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>
</div>
<div class="waiting-indicator">
<div class="pulse-dot"></div>
<span>Ожидание оплаты...</span>
<span>{{ 'cart.waitingPayment' | translate }}</span>
</div>
</div>
<div class="payment-actions">
<button class="copy-btn" (click)="copyPaymentLink()">
{{ linkCopied() ? '✓ Скопировано' : 'Скопировать ссылку' }}
{{ linkCopied() ? ('cart.copied' | translate) : ('cart.copyLink' | translate) }}
</button>
<a [href]="paymentUrl()" target="_blank" rel="noopener noreferrer" class="open-btn">
Открыть в новой вкладке
{{ 'cart.openNewTab' | translate }}
</a>
</div>
</div>
@@ -189,8 +189,8 @@
@if (paymentStatus() === 'success') {
<div class="payment-status-screen success">
<div class="success-icon"></div>
<h2>Поздравляем! Оплата прошла успешно!</h2>
<p class="success-text">Введите ваши контактные данные, и мы отправим вам покупку в течение нескольких минут</p>
<h2>{{ 'cart.paymentSuccess' | translate }}</h2>
<p class="success-text">{{ 'cart.paymentSuccessDesc' | translate }}</p>
<div class="email-form">
<div class="input-group">
@@ -236,9 +236,9 @@
>
@if (emailSubmitting()) {
<span class="spinner-small"></span>
Отправка...
{{ 'cart.sending' | translate }}
} @else {
Отправить
{{ 'cart.send' | translate }}
}
</button>
</div>
@@ -248,9 +248,9 @@
@if (paymentStatus() === 'timeout') {
<div class="payment-status-screen timeout">
<div class="timeout-icon"></div>
<h2>Время ожидания истекло</h2>
<p>Мы не получили подтверждение оплаты в течение 3 минут.</p>
<p class="auto-close">Окно закроется автоматически...</p>
<h2>{{ 'cart.paymentTimeout' | translate }}</h2>
<p>{{ 'cart.paymentTimeoutDesc' | translate }}</p>
<p class="auto-close">{{ 'cart.autoClose' | translate }}</p>
</div>
}
</div>

View File

@@ -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 { Router, RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms';
@@ -10,10 +10,12 @@ import { EmptyCartIconComponent } from '../../components/empty-cart-icon/empty-c
import { environment } from '../../../environments/environment';
import { getDiscountedPrice, getMainImage, trackByItemId } from '../../utils/item.utils';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe';
import { TranslateService } from '../../i18n/translate.service';
@Component({
selector: 'app-cart',
imports: [DecimalPipe, RouterLink, FormsModule, EmptyCartIconComponent, LangRoutePipe],
imports: [DecimalPipe, RouterLink, FormsModule, EmptyCartIconComponent, LangRoutePipe, TranslatePipe],
templateUrl: './cart.component.html',
styleUrls: ['./cart.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@@ -25,6 +27,8 @@ export class CartComponent implements OnDestroy {
termsAccepted = false;
isnovo = environment.theme === 'novo';
private i18n = inject(TranslateService);
// Swipe state
swipedItemId = signal<number | null>(null);
@@ -116,7 +120,7 @@ export class CartComponent implements OnDestroy {
}
clearCart(): void {
if (confirm('Вы уверены, что хотите очистить корзину?')) {
if (confirm(this.i18n.t('cart.confirmClear'))) {
this.cartService.clearCart();
}
}
@@ -127,7 +131,7 @@ export class CartComponent implements OnDestroy {
checkout(): void {
if (!this.termsAccepted) {
alert('Пожалуйста, примите условия оферты, политику возврата и возврата для подтверждения оформления заказа.');
alert(this.i18n.t('cart.acceptTerms'));
return;
}
this.openPaymentPopup();
@@ -249,7 +253,7 @@ export class CartComponent implements OnDestroy {
this.linkCopied.set(true);
setTimeout(() => this.linkCopied.set(false), 2000);
}).catch(err => {
console.error('Ошибка копирования:', err);
console.error(this.i18n.t('cart.copyError'), err);
});
}
}
@@ -314,7 +318,7 @@ export class CartComponent implements OnDestroy {
next: () => {
this.emailSubmitting.set(false);
// Show success message
alert('Email успешно отправлен! Проверьте свою почту.');
alert(this.i18n.t('cart.emailSuccess'));
// Close popup and redirect to home page
setTimeout(() => {
this.closePaymentPopup();
@@ -325,7 +329,7 @@ export class CartComponent implements OnDestroy {
error: (err) => {
console.error('Error submitting email:', err);
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) {
this.phoneError.set('Номер телефона обязателен');
this.phoneError.set(this.i18n.t('cart.phoneRequired'));
} 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) {
this.phoneError.set('Слишком много цифр');
this.phoneError.set(this.i18n.t('cart.phoneTooMany'));
} else {
this.phoneError.set('');
}
@@ -418,19 +422,19 @@ export class CartComponent implements OnDestroy {
}
if (email.length === 0) {
this.emailError.set('Email обязателен');
this.emailError.set(this.i18n.t('cart.emailRequired'));
} else if (email.length < 5) {
this.emailError.set('Email слишком короткий (минимум 5 символов)');
this.emailError.set(this.i18n.t('cart.emailTooShort'));
} else if (email.length > 100) {
this.emailError.set('Email слишком длинный (максимум 100 символов)');
this.emailError.set(this.i18n.t('cart.emailTooLong'));
} else if (!email.includes('@')) {
this.emailError.set('Email должен содержать @');
this.emailError.set(this.i18n.t('cart.emailNeedsAt'));
} else if (!email.includes('.')) {
this.emailError.set('Email должен содержать домен (.com, .ru и т.д.)');
this.emailError.set(this.i18n.t('cart.emailNeedsDomain'));
} else {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
this.emailError.set('Некорректный формат email');
this.emailError.set(this.i18n.t('cart.emailInvalid'));
} else {
this.emailError.set('');
}