diff --git a/src/app/app.html b/src/app/app.html index ee0a585..775fccf 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -1,14 +1,14 @@ @if (checkingServer()) {
-

Подключение к серверу...

+

{{ 'app.connecting' | translate }}

} @else if (!serverAvailable()) {
⚠️
-

Сервер недоступен

-

Не удалось подключиться к серверу. Проверьте подключение к интернету.

- +

{{ 'app.serverUnavailable' | translate }}

+

{{ 'app.serverError' | translate }}

+
} @else { diff --git a/src/app/app.ts b/src/app/app.ts index 63ad618..ccaab04 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -11,10 +11,12 @@ import { filter, first } from 'rxjs/operators'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { environment } from '../environments/environment'; import { SwUpdate } from '@angular/service-worker'; +import { TranslatePipe } from './i18n/translate.pipe'; +import { TranslateService } from './i18n/translate.service'; @Component({ selector: 'app-root', - imports: [RouterOutlet, HeaderComponent, FooterComponent, BackButtonComponent], + imports: [RouterOutlet, HeaderComponent, FooterComponent, BackButtonComponent, TranslatePipe], templateUrl: './app.html', styleUrl: './app.scss' }) @@ -30,9 +32,10 @@ export class App implements OnInit { private swUpdate = inject(SwUpdate); private appRef = inject(ApplicationRef); private router = inject(Router); + private i18n = inject(TranslateService); ngOnInit(): void { - this.titleService.setTitle(`${environment.brandFullName} - Маркетплейс товаров и услуг`); + this.titleService.setTitle(`${environment.brandFullName} - ${this.i18n.t('app.pageTitle')}`); this.checkServerHealth(); this.setupAutoUpdates(); diff --git a/src/app/components/back-button/back-button.component.ts b/src/app/components/back-button/back-button.component.ts index 4eae8f8..48de323 100644 --- a/src/app/components/back-button/back-button.component.ts +++ b/src/app/components/back-button/back-button.component.ts @@ -1,12 +1,13 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Component, ChangeDetectionStrategy, inject } from '@angular/core'; import { Location } from '@angular/common'; import { environment } from '../../../environments/environment'; +import { TranslateService } from '../../i18n/translate.service'; @Component({ selector: 'app-back-button', template: ` @if (!isnovo) { - } @@ -16,9 +16,9 @@
-

Корзина пуста

-

Добавьте товары, чтобы начать покупки

- Перейти к покупкам +

{{ 'cart.empty' | translate }}

+

{{ 'cart.emptyDesc' | translate }}

+ {{ 'cart.goShopping' | translate }} } @@ -86,21 +86,21 @@
-

Итого

+

{{ 'cart.total' | translate }}

- Товары ({{ itemCount() }}) + {{ 'cart.items' | translate }} ({{ itemCount() }}) {{ totalPrice() | number:'1.2-2' }} ₽
- Доставка + {{ 'cart.deliveryLabel' | translate }} 0 ₽
- К оплате + {{ 'cart.toPay' | translate }} {{ totalPrice() | number:'1.2-2' }} ₽
@@ -113,11 +113,11 @@ /> - Я согласен с - публичной офертой, - политикой возврата, - условиями гарантии и - политикой конфиденциальности + {{ 'cart.agreeWith' | translate }} + {{ 'cart.publicOffer' | translate }}, + {{ 'cart.returnPolicy' | translate }}, + {{ 'cart.guaranteeTerms' | translate }} {{ 'cart.and' | translate }} + {{ 'cart.privacyPolicy' | translate }}
@@ -128,7 +128,7 @@ [class.disabled]="!termsAccepted" [disabled]="!termsAccepted" > - Оформить заказ + {{ 'cart.checkout' | translate }} @@ -139,7 +139,7 @@ @if (showPaymentPopup()) {
- - Открыть в новой вкладке + {{ 'cart.openNewTab' | translate }}
@@ -189,8 +189,8 @@ @if (paymentStatus() === 'success') {
-

Поздравляем! Оплата прошла успешно!

-

Введите ваши контактные данные, и мы отправим вам покупку в течение нескольких минут

+

{{ 'cart.paymentSuccess' | translate }}

+

{{ 'cart.paymentSuccessDesc' | translate }}

@@ -236,9 +236,9 @@ > @if (emailSubmitting()) { - Отправка... + {{ 'cart.sending' | translate }} } @else { - Отправить + {{ 'cart.send' | translate }} }
@@ -248,9 +248,9 @@ @if (paymentStatus() === 'timeout') {
-

Время ожидания истекло

-

Мы не получили подтверждение оплаты в течение 3 минут.

-

Окно закроется автоматически...

+

{{ 'cart.paymentTimeout' | translate }}

+

{{ 'cart.paymentTimeoutDesc' | translate }}

+

{{ 'cart.autoClose' | translate }}

}
diff --git a/src/app/pages/cart/cart.component.ts b/src/app/pages/cart/cart.component.ts index 8d7a762..df11865 100644 --- a/src/app/pages/cart/cart.component.ts +++ b/src/app/pages/cart/cart.component.ts @@ -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(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(''); } diff --git a/src/app/pages/category/category.component.html b/src/app/pages/category/category.component.html index b136512..9e10629 100644 --- a/src/app/pages/category/category.component.html +++ b/src/app/pages/category/category.component.html @@ -2,7 +2,7 @@ @if (error()) {

{{ error() }}

- +
} @@ -46,7 +46,7 @@
} @@ -55,13 +55,13 @@ @if (loading() && items().length > 0) {
-

Загрузка...

+

{{ 'category.loadingMore' | translate }}

} @if (!hasMore() && items().length > 0) {
-

Все товары загружены

+

{{ 'category.allLoaded' | translate }}

} @@ -75,16 +75,16 @@ -

Упс! Здесь пока пусто

-

В этой категории ещё нет товаров, но скоро они появятся

- На главную +

{{ 'category.emptyTitle' | translate }}

+

{{ 'category.emptyDesc' | translate }}

+ {{ 'category.goHome' | translate }} } @if (loading() && items().length === 0) {
-

Загрузка товаров...

+

{{ 'category.loading' | translate }}

} } diff --git a/src/app/pages/category/category.component.ts b/src/app/pages/category/category.component.ts index 28e5ba3..8e3dc6d 100644 --- a/src/app/pages/category/category.component.ts +++ b/src/app/pages/category/category.component.ts @@ -6,10 +6,11 @@ import { Item } from '../../models'; import { Subscription } from 'rxjs'; import { getDiscountedPrice, getMainImage, trackByItemId } from '../../utils/item.utils'; import { LangRoutePipe } from '../../pipes/lang-route.pipe'; +import { TranslatePipe } from '../../i18n/translate.pipe'; @Component({ selector: 'app-category', - imports: [DecimalPipe, RouterLink, LangRoutePipe], + imports: [DecimalPipe, RouterLink, LangRoutePipe, TranslatePipe], templateUrl: './category.component.html', styleUrls: ['./category.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush diff --git a/src/app/pages/category/subcategories.component.html b/src/app/pages/category/subcategories.component.html index 0ca74e6..c117e1f 100644 --- a/src/app/pages/category/subcategories.component.html +++ b/src/app/pages/category/subcategories.component.html @@ -2,14 +2,14 @@ @if (loading()) {
-

Загрузка подкатегорий...

+

{{ 'subcategories.loading' | translate }}

} @if (error()) {

{{ error() }}

- +
} @@ -45,9 +45,9 @@ -

Упс! Подкатегорий пока нет

-

В этом разделе ещё нет подкатегорий, но скоро они появятся

- На главную +

{{ 'subcategories.emptyTitle' | translate }}

+

{{ 'subcategories.emptyDesc' | translate }}

+ {{ 'subcategories.goHome' | translate }} } } diff --git a/src/app/pages/category/subcategories.component.ts b/src/app/pages/category/subcategories.component.ts index b41f4a3..310ee91 100644 --- a/src/app/pages/category/subcategories.component.ts +++ b/src/app/pages/category/subcategories.component.ts @@ -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 { ApiService, LanguageService } from '../../services'; import { Category } from '../../models'; import { Subscription } from 'rxjs'; import { LangRoutePipe } from '../../pipes/lang-route.pipe'; +import { TranslatePipe } from '../../i18n/translate.pipe'; +import { TranslateService } from '../../i18n/translate.service'; @Component({ selector: 'app-subcategories', - imports: [RouterLink, LangRoutePipe], + imports: [RouterLink, LangRoutePipe, TranslatePipe], templateUrl: './subcategories.component.html', styleUrls: ['./subcategories.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush @@ -17,6 +19,8 @@ export class SubcategoriesComponent implements OnInit, OnDestroy { subcategories = signal([]); loading = signal(true); error = signal(null); + + private i18n = inject(TranslateService); parentName = signal(''); private routeSubscription?: Subscription; @@ -46,7 +50,7 @@ export class SubcategoriesComponent implements OnInit, OnDestroy { this.categories.set(cats); const subs = cats.filter(c => c.parentID === 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) { // No subcategories: redirect to items list for this category diff --git a/src/app/pages/home/home.component.html b/src/app/pages/home/home.component.html index 7427885..c6c0806 100644 --- a/src/app/pages/home/home.component.html +++ b/src/app/pages/home/home.component.html @@ -3,10 +3,10 @@
-

Добро пожаловать в {{ brandName }}

-

Найдите всё, что нужно, в одном месте

+

{{ 'home.welcomeTo' | translate:{ brand: brandName } }}

+

{{ 'home.subtitle' | translate }}

- Начать поиск + {{ 'home.startSearch' | translate }} @@ -21,31 +21,31 @@ @if (loading()) {
-

Загружаем категории...

+

​{{ 'home.loading' | translate }}

} @if (error()) {
⚠️
-

Что-то пошло не так

+

{{ 'home.errorTitle' | translate }}

{{ error() }}

- +
} @if (!loading() && !error()) {
-

Категории товаров

-

Выберите интересующую категорию

+

{{ 'home.categoriesTitle' | translate }}

+

{{ 'home.categoriesSubtitle' | translate }}

@if (topLevelCategories().length === 0) {
📦
-

Категории скоро появятся

-

Мы работаем над наполнением каталога

+

{{ 'home.categoriesEmpty' | translate }}

+

{{ 'home.categoriesEmptyDesc' | translate }}

} @else {
@@ -78,16 +78,16 @@
-

Здесь ты найдёшь всё

-

Тысячи товаров в одном месте

-

просто и удобно

+

{{ 'home.dexarHeroTitle' | translate }}

+

{{ 'home.dexarHeroSubtitle' | translate }}

+

{{ 'home.dexarHeroTagline' | translate }}

- Перейти в каталог + {{ 'home.goToCatalog' | translate }} +
} @if (!loading() && !error()) {
-

Каталог товаров

+

{{ 'home.catalogTitle' | translate }}

@if (topLevelCategories().length === 0) {
📦
-

Категории пока отсутствуют

-

Скоро здесь появятся категории товаров

+

{{ 'home.emptyCategoriesDexar' | translate }}

+

{{ 'home.categoriesSoonDexar' | translate }}

} @else {
@@ -140,7 +140,7 @@

{{ category.name }}

-

{{ getItemCount(category.categoryID) }} товаров

+

{{ 'home.itemsCount' | translate:{ count: getItemCount(category.categoryID) } }}

} diff --git a/src/app/pages/home/home.component.ts b/src/app/pages/home/home.component.ts index 6a1856d..c931d09 100644 --- a/src/app/pages/home/home.component.ts +++ b/src/app/pages/home/home.component.ts @@ -5,10 +5,11 @@ import { Category } from '../../models'; import { environment } from '../../../environments/environment'; import { ItemsCarouselComponent } from '../../components/items-carousel/items-carousel.component'; import { LangRoutePipe } from '../../pipes/lang-route.pipe'; +import { TranslatePipe } from '../../i18n/translate.pipe'; @Component({ selector: 'app-home', - imports: [RouterLink, ItemsCarouselComponent, LangRoutePipe], + imports: [RouterLink, ItemsCarouselComponent, LangRoutePipe, TranslatePipe], templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush diff --git a/src/app/pages/item-detail/item-detail.component.html b/src/app/pages/item-detail/item-detail.component.html index 4dbb3f1..a66bacc 100644 --- a/src/app/pages/item-detail/item-detail.component.html +++ b/src/app/pages/item-detail/item-detail.component.html @@ -4,14 +4,14 @@ @if (loading()) {
-

Загрузка...

+

{{ 'itemDetail.loading' | translate }}

} @if (error()) { } @@ -49,7 +49,7 @@ -

Нет изображения

+

{{ 'itemDetail.noImage' | translate }}

}
@@ -76,10 +76,10 @@
- Наличие: + {{ 'itemDetail.stock' | translate }}
- {{ item()!.remainings === 'high' ? 'В наличии' : item()!.remainings === 'medium' ? 'Мало' : 'Осталось немного' }} + {{ item()!.remainings === 'high' ? ('itemDetail.inStock' | translate) : item()!.remainings === 'medium' ? ('itemDetail.mediumStock' | translate) : ('itemDetail.lowStock' | translate) }}
@@ -89,24 +89,24 @@ - Добавить в корзину + {{ 'itemDetail.addToCart' | translate }}
-

Описание

+

{{ 'itemDetail.description' | translate }}

-

Отзывы ({{ item()!.callbacks?.length || 0 }})

+

{{ 'itemDetail.reviews' | translate }} ({{ item()!.callbacks?.length || 0 }})

-

Ваш отзыв

+

{{ 'itemDetail.yourReview' | translate }}

- +
@for (star of [1, 2, 3, 4, 5]; track star) {
@if (!newReview.anonymous && getUserDisplayName()) { {{ getUserDisplayName() }} @@ -139,12 +139,12 @@ [class.submitting]="reviewSubmitStatus() === 'loading'"> @if (reviewSubmitStatus() === 'loading') { - Отправка... + {{ 'itemDetail.submitting' | translate }} } @else { - Отправить + {{ 'itemDetail.submit' | translate }} }
@@ -154,7 +154,7 @@ - Спасибо за ваш отзыв! + {{ 'itemDetail.reviewSuccess' | translate }}
} @@ -163,7 +163,7 @@ - Ошибка отправки. Попробуйте позже. + {{ 'itemDetail.reviewError' | translate }}
}
@@ -174,7 +174,7 @@
- {{ review.userID || 'Пользователь' }} + {{ review.userID ? review.userID : ('itemDetail.defaultUser' | translate) }} @if (review.timestamp) { {{ formatDate(review.timestamp) }} } @@ -185,7 +185,7 @@
} } @else { -

Пока нет отзывов. Станьте первым!

+

{{ 'itemDetail.noReviews' | translate }}

}
@@ -197,14 +197,14 @@ @if (loading()) {
-

Загрузка товара...

+

{{ 'itemDetail.loadingDexar' | translate }}

} @if (error()) { } @@ -222,7 +222,7 @@ @if (photo.video) {
} - +
} @@ -241,7 +241,7 @@ - Нет изображения + {{ 'itemDetail.noImage' | translate }} } @@ -260,7 +260,7 @@ } {{ item()!.rating }} - ({{ item()!.callbacks?.length || 0 }} отзывов) + ({{ item()!.callbacks?.length || 0 }} {{ 'itemDetail.reviewsCount' | translate }})
@@ -276,13 +276,13 @@
- Наличие: + {{ 'itemDetail.stock' | translate }} - {{ item()!.remainings === 'high' ? 'В наличии' : item()!.remainings === 'medium' ? 'Заканчивается' : 'Последние штуки' }} + {{ item()!.remainings === 'high' ? ('itemDetail.inStock' | translate) : item()!.remainings === 'medium' ? ('itemDetail.mediumStock' | translate) : ('itemDetail.lastItems' | translate) }}
@@ -292,11 +292,11 @@ - Добавить в корзину + {{ 'itemDetail.addToCart' | translate }}
-

Описание

+

{{ 'itemDetail.description' | translate }}

@@ -304,12 +304,12 @@
-

Отзывы ({{ item()!.callbacks?.length || 0 }})

+

{{ 'itemDetail.reviews' | translate }} ({{ item()!.callbacks?.length || 0 }})

-

Оставить отзыв

+

{{ 'itemDetail.leaveReview' | translate }}

- +
@for (star of [1, 2, 3, 4, 5]; track star) {
@if (!newReview.anonymous && getUserDisplayName()) { {{ getUserDisplayName() }} @@ -342,9 +342,9 @@ [class.submitting]="reviewSubmitStatus() === 'loading'"> @if (reviewSubmitStatus() === 'loading') { - Отправка... + {{ 'itemDetail.submitting' | translate }} } @else { - Отправить + {{ 'itemDetail.submit' | translate }} }
@@ -352,14 +352,14 @@ @if (reviewSubmitStatus() === 'success') {
- Спасибо за ваш отзыв! + {{ 'itemDetail.reviewSuccess' | translate }}
} @if (reviewSubmitStatus() === 'error') {
- Ошибка отправки. Попробуйте позже. + {{ 'itemDetail.reviewError' | translate }}
}
@@ -370,7 +370,7 @@
- {{ callback.userID || 'Аноним' }} + {{ callback.userID ? callback.userID : ('itemDetail.defaultUser' | translate) }} @if (callback.timestamp) { {{ formatDate(callback.timestamp) }} } @@ -391,7 +391,7 @@
} } @else { -

Пока нет отзывов. Станьте первым!

+

{{ 'itemDetail.noReviews' | translate }}

}
@@ -399,7 +399,7 @@ @if (item()!.questions && item()!.questions!.length > 0) {
-

Вопросы и ответы ({{ item()!.questions!.length }})

+

{{ 'itemDetail.qna' | translate }} ({{ item()!.questions!.length }})

@for (question of item()!.questions!; track $index) {
diff --git a/src/app/pages/item-detail/item-detail.component.ts b/src/app/pages/item-detail/item-detail.component.ts index b18e95d..8009ce6 100644 --- a/src/app/pages/item-detail/item-detail.component.ts +++ b/src/app/pages/item-detail/item-detail.component.ts @@ -10,10 +10,12 @@ import { environment } from '../../../environments/environment'; import { SecurityContext } from '@angular/core'; import { getDiscountedPrice } 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-item-detail', - imports: [DecimalPipe, RouterLink, FormsModule, LangRoutePipe], + imports: [DecimalPipe, RouterLink, FormsModule, LangRoutePipe, TranslatePipe], templateUrl: './item-detail.component.html', styleUrls: ['./item-detail.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush @@ -39,6 +41,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy { private reloadTimeout?: ReturnType; private seoService = inject(SeoService); + private i18n = inject(TranslateService); constructor( private route: ActivatedRoute, @@ -115,10 +118,10 @@ export class ItemDetailComponent implements OnInit, OnDestroy { const diffMs = now.getTime() - date.getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); - if (diffDays === 0) return 'Сегодня'; - if (diffDays === 1) return 'Вчера'; - if (diffDays < 7) return `${diffDays} дн. назад`; - if (diffDays < 30) return `${Math.floor(diffDays / 7)} нед. назад`; + if (diffDays === 0) return this.i18n.t('itemDetail.today'); + if (diffDays === 1) return this.i18n.t('itemDetail.yesterday'); + if (diffDays < 7) return `${diffDays} ${this.i18n.t('itemDetail.daysAgo')}`; + if (diffDays < 30) return `${Math.floor(diffDays / 7)} ${this.i18n.t('itemDetail.weeksAgo')}`; return date.toLocaleDateString('ru-RU', { day: 'numeric', @@ -133,7 +136,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy { getUserDisplayName(): string | null { if (!this.telegramService.isTelegramApp()) { - return 'Пользователь'; + return this.i18n.t('itemDetail.defaultUser'); } return this.telegramService.getDisplayName(); } diff --git a/src/app/pages/search/search.component.html b/src/app/pages/search/search.component.html index c94e96b..6100abe 100644 --- a/src/app/pages/search/search.component.html +++ b/src/app/pages/search/search.component.html @@ -1,12 +1,12 @@
-

Поиск товаров

+

{{ 'search.title' | translate }}

-

Ничего не найдено

-

По запросу "{{ searchQuery }}" товары не найдены

-

Попробуйте изменить запрос или используйте другие ключевые слова

+

{{ 'search.noResults' | translate }}

+

{{ 'search.noResultsFor' | translate:{ query: searchQuery } }}

+

{{ 'search.noResultsHint' | translate }}

} @@ -95,7 +95,7 @@
} @@ -104,13 +104,13 @@ @if (loading() && items().length > 0) {
-

Загрузка...

+

{{ 'search.loadingMore' | translate }}

} @if (!hasMore() && items().length > 0) {
-

Все результаты загружены

+

{{ 'search.allLoaded' | translate }}

} } @@ -123,7 +123,7 @@
-

Введите запрос для поиска товаров

+

{{ 'search.emptyState' | translate }}

}
diff --git a/src/app/pages/search/search.component.ts b/src/app/pages/search/search.component.ts index 32f55a9..48eda11 100644 --- a/src/app/pages/search/search.component.ts +++ b/src/app/pages/search/search.component.ts @@ -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 { FormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; @@ -8,10 +8,12 @@ import { Subject, Subscription } from 'rxjs'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { getDiscountedPrice, getMainImage, trackByItemId } from '../../utils/item.utils'; import { LangRoutePipe } from '../../pipes/lang-route.pipe'; +import { TranslatePipe } from '../../i18n/translate.pipe'; +import { TranslateService } from '../../i18n/translate.service'; @Component({ selector: 'app-search', - imports: [DecimalPipe, FormsModule, RouterLink, LangRoutePipe], + imports: [DecimalPipe, FormsModule, RouterLink, LangRoutePipe, TranslatePipe], templateUrl: './search.component.html', styleUrls: ['./search.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush @@ -29,6 +31,7 @@ export class SearchComponent implements OnDestroy { private isLoadingMore = false; private searchSubject = new Subject(); private searchSubscription: Subscription; + private i18n = inject(TranslateService); constructor( private apiService: ApiService, @@ -100,7 +103,7 @@ export class SearchComponent implements OnDestroy { this.isLoadingMore = false; }, error: (err) => { - this.error.set('Ошибка при поиске товаров'); + this.error.set(this.i18n.t('home.errorTitle')); this.loading.set(false); this.isLoadingMore = false; console.error('Error searching items:', err); diff --git a/src/app/services/seo.service.ts b/src/app/services/seo.service.ts index 0e4673c..46597d1 100644 --- a/src/app/services/seo.service.ts +++ b/src/app/services/seo.service.ts @@ -55,8 +55,8 @@ export class SeoService { * Reset meta tags back to defaults (call on navigation away from item page). */ resetToDefaults(): void { - const defaultTitle = `${this.siteName} — Маркетплейс товаров и услуг`; - const defaultDescription = 'Современный маркетплейс для покупки цифровых товаров. Широкий выбор товаров, удобный поиск, быстрая доставка.'; + const defaultTitle = `${this.siteName} — Marketplace`; + const defaultDescription = 'Modern marketplace for buying digital goods. Wide selection, convenient search, fast delivery.'; const defaultImage = `${this.siteUrl}/og-image.jpg`; this.title.setTitle(defaultTitle); diff --git a/src/app/services/telegram.service.ts b/src/app/services/telegram.service.ts index 7943e83..5bb1c77 100644 --- a/src/app/services/telegram.service.ts +++ b/src/app/services/telegram.service.ts @@ -34,6 +34,6 @@ export class TelegramService { } getDisplayName(): string { - return this.getUsername() || this.getFullName() || 'Пользователь'; + return this.getUsername() || this.getFullName() || 'User'; } }