Merge pull request 'localization' (#2) from localization into main
Reviewed-on: #2
This commit is contained in:
160
LOCALIZATION.md
Normal file
160
LOCALIZATION.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# Система локализации Telegram Tinder Bot
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
Система локализации обеспечивает многоязычную поддержку бота с использованием i18next для интерфейса и DeepSeek AI для перевода анкет пользователей.
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
### Компоненты системы
|
||||||
|
|
||||||
|
1. **LocalizationService** - основной сервис локализации интерфейса
|
||||||
|
2. **DeepSeekTranslationService** - сервис для перевода анкет с помощью AI
|
||||||
|
3. **TranslationController** - контроллер для управления переводами
|
||||||
|
4. **Файлы переводов** - JSON файлы с переводами для каждого языка
|
||||||
|
|
||||||
|
### Поддерживаемые языки
|
||||||
|
|
||||||
|
- 🇷🇺 Русский (ru) - по умолчанию
|
||||||
|
- 🇺🇸 Английский (en)
|
||||||
|
- 🇪🇸 Испанский (es)
|
||||||
|
- 🇫🇷 Французский (fr)
|
||||||
|
- 🇩🇪 Немецкий (de)
|
||||||
|
- 🇮🇹 Итальянский (it)
|
||||||
|
- 🇵🇹 Португальский (pt)
|
||||||
|
- 🇨🇳 Китайский (zh)
|
||||||
|
- 🇯🇵 Японский (ja)
|
||||||
|
- 🇰🇷 Корейский (ko)
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### Локализация интерфейса
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { t } from '../services/localizationService';
|
||||||
|
|
||||||
|
// Простой перевод
|
||||||
|
const message = t('welcome.greeting');
|
||||||
|
|
||||||
|
// Перевод с параметрами
|
||||||
|
const message = t('profile.ageRange', { min: 18, max: 65 });
|
||||||
|
|
||||||
|
// Установка языка пользователя
|
||||||
|
localizationService.setLanguage('en');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Структура файлов переводов
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "Добро пожаловать в Telegram Tinder Bot! 💕",
|
||||||
|
"description": "Найди свою вторую половинку прямо здесь!"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"name": "Имя",
|
||||||
|
"age": "Возраст",
|
||||||
|
"bio": "О себе"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Перевод анкет (Premium функция)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import DeepSeekTranslationService from '../services/deepSeekTranslationService';
|
||||||
|
|
||||||
|
const translationService = DeepSeekTranslationService.getInstance();
|
||||||
|
|
||||||
|
// Перевод текста анкеты
|
||||||
|
const result = await translationService.translateProfile({
|
||||||
|
text: "Привет! Я люблю путешествовать и читать книги.",
|
||||||
|
targetLanguage: 'en',
|
||||||
|
sourceLanguage: 'ru'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Настройка
|
||||||
|
|
||||||
|
### Переменные окружения
|
||||||
|
|
||||||
|
```env
|
||||||
|
# DeepSeek API для перевода анкет
|
||||||
|
DEEPSEEK_API_KEY=your_deepseek_api_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
### База данных
|
||||||
|
|
||||||
|
Таблица `users` содержит поле `language` для хранения предпочитаемого языка пользователя:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN language VARCHAR(5) DEFAULT 'ru';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Функции
|
||||||
|
|
||||||
|
### Автоматическое определение языка
|
||||||
|
|
||||||
|
- При регистрации пользователя язык определяется по `language_code` из Telegram
|
||||||
|
- Пользователь может изменить язык в настройках
|
||||||
|
- Поддерживается определение языка текста для перевода
|
||||||
|
|
||||||
|
### Премиум функции перевода
|
||||||
|
|
||||||
|
- **Перевод анкет** - доступен только для премиум пользователей
|
||||||
|
- **AI-перевод** - используется DeepSeek API для качественного перевода
|
||||||
|
- **Контекстный перевод** - сохраняется тон и стиль исходного текста
|
||||||
|
|
||||||
|
### Клавиатуры и меню
|
||||||
|
|
||||||
|
Все кнопки и меню автоматически локализуются на основе языка пользователя:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Пример создания локализованной клавиатуры
|
||||||
|
public getLanguageSelectionKeyboard() {
|
||||||
|
return {
|
||||||
|
inline_keyboard: [
|
||||||
|
[
|
||||||
|
{ text: '🇷🇺 Русский', callback_data: 'set_language_ru' },
|
||||||
|
{ text: '🇺🇸 English', callback_data: 'set_language_en' }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Расширение
|
||||||
|
|
||||||
|
### Добавление нового языка
|
||||||
|
|
||||||
|
1. Создать файл перевода `src/locales/{language_code}.json`
|
||||||
|
2. Добавить язык в массив поддерживаемых языков в `LocalizationService`
|
||||||
|
3. Обновить ограничение в базе данных
|
||||||
|
4. Добавить кнопку в меню выбора языка
|
||||||
|
|
||||||
|
### Добавление новых переводов
|
||||||
|
|
||||||
|
1. Добавить ключи в основной файл перевода (`ru.json`)
|
||||||
|
2. Перевести на все поддерживаемые языки
|
||||||
|
3. Использовать в коде через функцию `t()`
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
- API ключ DeepSeek хранится в переменных окружения
|
||||||
|
- Проверка премиум статуса перед доступом к переводу
|
||||||
|
- Ограничение по количеству запросов к API
|
||||||
|
- Таймауты для предотвращения зависания
|
||||||
|
|
||||||
|
## Мониторинг
|
||||||
|
|
||||||
|
- Логирование ошибок перевода
|
||||||
|
- Отслеживание использования API
|
||||||
|
- Статистика по языкам пользователей
|
||||||
|
|
||||||
|
## Производительность
|
||||||
|
|
||||||
|
- Кэширование переводов интерфейса
|
||||||
|
- Ленивая загрузка файлов переводов
|
||||||
|
- Асинхронная обработка запросов к DeepSeek API
|
||||||
|
- Индексы в базе данных для быстрого поиска по языку
|
||||||
105
VIP_FUNCTIONS.md
Normal file
105
VIP_FUNCTIONS.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# VIP Функции - Документация
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
Реализованы VIP функции с проверкой премиум статуса пользователя в базе данных.
|
||||||
|
|
||||||
|
## База данных
|
||||||
|
|
||||||
|
### Новые поля в таблице users:
|
||||||
|
- `premium` (BOOLEAN) - флаг премиум статуса
|
||||||
|
- `premium_expires_at` (TIMESTAMP) - дата окончания премиум
|
||||||
|
|
||||||
|
## Логика работы
|
||||||
|
|
||||||
|
### 1. Кнопка "VIP Поиск"
|
||||||
|
- **Если premium = false**: показывает информацию о премиум и предложение купить
|
||||||
|
- **Если premium = true**: открывает VIP поиск с фильтрами
|
||||||
|
|
||||||
|
### 2. VIP Поиск включает:
|
||||||
|
|
||||||
|
#### Быстрый VIP поиск
|
||||||
|
- Только пользователи с фото
|
||||||
|
- Только онлайн пользователи
|
||||||
|
|
||||||
|
#### Расширенный поиск
|
||||||
|
- Фильтр по возрасту
|
||||||
|
- Фильтр по городу
|
||||||
|
- Фильтр по целям знакомства
|
||||||
|
- Фильтр по хобби
|
||||||
|
- Фильтр по образу жизни
|
||||||
|
|
||||||
|
#### Поиск по целям знакомства
|
||||||
|
- Серьезные отношения
|
||||||
|
- Общение и дружба
|
||||||
|
- Развлечения
|
||||||
|
- Деловые знакомства
|
||||||
|
|
||||||
|
#### Поиск по хобби
|
||||||
|
- Фильтрация по массиву хобби в профиле
|
||||||
|
|
||||||
|
## Файлы
|
||||||
|
|
||||||
|
### Новые файлы:
|
||||||
|
- `src/services/vipService.ts` - сервис для работы с VIP функциями
|
||||||
|
- `src/controllers/vipController.ts` - контроллер VIP поиска
|
||||||
|
- `src/database/migrations/add_premium_field.sql` - миграция для premium полей
|
||||||
|
|
||||||
|
### Изменённые файлы:
|
||||||
|
- `src/handlers/callbackHandlers.ts` - добавлены VIP обработчики
|
||||||
|
|
||||||
|
## Методы VipService
|
||||||
|
|
||||||
|
### checkPremiumStatus(telegramId: string)
|
||||||
|
Проверяет премиум статус пользователя, автоматически убирает истёкший премиум.
|
||||||
|
|
||||||
|
### addPremium(telegramId: string, durationDays: number)
|
||||||
|
Добавляет премиум статус на указанное количество дней.
|
||||||
|
|
||||||
|
### vipSearch(telegramId: string, filters: VipSearchFilters)
|
||||||
|
Выполняет VIP поиск с фильтрами (только для премиум пользователей).
|
||||||
|
|
||||||
|
### getPremiumFeatures()
|
||||||
|
Возвращает описание премиум возможностей.
|
||||||
|
|
||||||
|
## Методы VipController
|
||||||
|
|
||||||
|
### showVipSearch(chatId, telegramId)
|
||||||
|
Основной метод - показывает VIP поиск или информацию о премиум.
|
||||||
|
|
||||||
|
### performQuickVipSearch(chatId, telegramId)
|
||||||
|
Быстрый VIP поиск (фото + онлайн).
|
||||||
|
|
||||||
|
### showDatingGoalSearch(chatId, telegramId)
|
||||||
|
Показывает поиск по целям знакомства.
|
||||||
|
|
||||||
|
## Тестирование
|
||||||
|
|
||||||
|
### Добавить премиум пользователю:
|
||||||
|
```sql
|
||||||
|
UPDATE users SET premium = true, premium_expires_at = NOW() + INTERVAL '30 days'
|
||||||
|
WHERE telegram_id = 'YOUR_TELEGRAM_ID';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Убрать премиум:
|
||||||
|
```sql
|
||||||
|
UPDATE users SET premium = false, premium_expires_at = NULL
|
||||||
|
WHERE telegram_id = 'YOUR_TELEGRAM_ID';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Callback данные
|
||||||
|
|
||||||
|
- `get_vip` / `vip_search` - показать VIP поиск
|
||||||
|
- `vip_quick_search` - быстрый VIP поиск
|
||||||
|
- `vip_advanced_search` - расширенный поиск
|
||||||
|
- `vip_dating_goal_search` - поиск по целям
|
||||||
|
- `vip_goal_{goal}` - поиск по конкретной цели
|
||||||
|
- `vip_like_{telegramId}` - VIP лайк
|
||||||
|
- `vip_superlike_{telegramId}` - VIP супер-лайк
|
||||||
|
- `vip_dislike_{telegramId}` - VIP дизлайк
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
- Все VIP функции проверяют премиум статус
|
||||||
|
- Автоматическое удаление истёкшего премиум
|
||||||
|
- Валидация всех входных данных
|
||||||
|
- Проверка существования пользователей перед операциями
|
||||||
49
package-lock.json
generated
49
package-lock.json
generated
@@ -10,8 +10,9 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node-telegram-bot-api": "^0.64.11",
|
"@types/node-telegram-bot-api": "^0.64.11",
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.12.1",
|
||||||
"dotenv": "^16.6.1",
|
"dotenv": "^16.6.1",
|
||||||
|
"i18next": "^25.5.2",
|
||||||
"node-telegram-bot-api": "^0.64.0",
|
"node-telegram-bot-api": "^0.64.0",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"sharp": "^0.32.6",
|
"sharp": "^0.32.6",
|
||||||
@@ -438,6 +439,14 @@
|
|||||||
"@babel/core": "^7.0.0-0"
|
"@babel/core": "^7.0.0-0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.28.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||||
|
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.27.2",
|
"version": "7.27.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||||
@@ -1349,9 +1358,9 @@
|
|||||||
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="
|
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.12.0",
|
"version": "1.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.1.tgz",
|
||||||
"integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
|
"integrity": "sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
"form-data": "^4.0.4",
|
"form-data": "^4.0.4",
|
||||||
@@ -2993,6 +3002,36 @@
|
|||||||
"node": ">=10.17.0"
|
"node": ">=10.17.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18next": {
|
||||||
|
"version": "25.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.2.tgz",
|
||||||
|
"integrity": "sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com/i18next.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.27.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ieee754": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
@@ -6255,7 +6294,7 @@
|
|||||||
"version": "5.9.2",
|
"version": "5.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|||||||
@@ -6,14 +6,15 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node dist/bot.js",
|
"start": "node dist/bot.js",
|
||||||
"dev": "ts-node src/bot.ts",
|
"dev": "ts-node src/bot.ts",
|
||||||
"build": "tsc",
|
"build": "tsc && cp -r src/locales dist/",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"db:init": "ts-node src/scripts/initDb.ts"
|
"db:init": "ts-node src/scripts/initDb.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node-telegram-bot-api": "^0.64.11",
|
"@types/node-telegram-bot-api": "^0.64.11",
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.12.1",
|
||||||
"dotenv": "^16.6.1",
|
"dotenv": "^16.6.1",
|
||||||
|
"i18next": "^25.5.2",
|
||||||
"node-telegram-bot-api": "^0.64.0",
|
"node-telegram-bot-api": "^0.64.0",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"sharp": "^0.32.6",
|
"sharp": "^0.32.6",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { testConnection, query } from './database/connection';
|
|||||||
import { ProfileService } from './services/profileService';
|
import { ProfileService } from './services/profileService';
|
||||||
import { MatchingService } from './services/matchingService';
|
import { MatchingService } from './services/matchingService';
|
||||||
import { NotificationService } from './services/notificationService';
|
import { NotificationService } from './services/notificationService';
|
||||||
|
import LocalizationService from './services/localizationService';
|
||||||
import { CommandHandlers } from './handlers/commandHandlers';
|
import { CommandHandlers } from './handlers/commandHandlers';
|
||||||
import { CallbackHandlers } from './handlers/callbackHandlers';
|
import { CallbackHandlers } from './handlers/callbackHandlers';
|
||||||
import { MessageHandlers } from './handlers/messageHandlers';
|
import { MessageHandlers } from './handlers/messageHandlers';
|
||||||
@@ -13,6 +14,7 @@ class TelegramTinderBot {
|
|||||||
private profileService: ProfileService;
|
private profileService: ProfileService;
|
||||||
private matchingService: MatchingService;
|
private matchingService: MatchingService;
|
||||||
private notificationService: NotificationService;
|
private notificationService: NotificationService;
|
||||||
|
private localizationService: LocalizationService;
|
||||||
private commandHandlers: CommandHandlers;
|
private commandHandlers: CommandHandlers;
|
||||||
private callbackHandlers: CallbackHandlers;
|
private callbackHandlers: CallbackHandlers;
|
||||||
private messageHandlers: MessageHandlers;
|
private messageHandlers: MessageHandlers;
|
||||||
@@ -27,6 +29,7 @@ class TelegramTinderBot {
|
|||||||
this.profileService = new ProfileService();
|
this.profileService = new ProfileService();
|
||||||
this.matchingService = new MatchingService();
|
this.matchingService = new MatchingService();
|
||||||
this.notificationService = new NotificationService(this.bot);
|
this.notificationService = new NotificationService(this.bot);
|
||||||
|
this.localizationService = LocalizationService.getInstance();
|
||||||
|
|
||||||
this.commandHandlers = new CommandHandlers(this.bot);
|
this.commandHandlers = new CommandHandlers(this.bot);
|
||||||
this.messageHandlers = new MessageHandlers(this.bot);
|
this.messageHandlers = new MessageHandlers(this.bot);
|
||||||
@@ -41,6 +44,9 @@ class TelegramTinderBot {
|
|||||||
try {
|
try {
|
||||||
console.log('🚀 Initializing Telegram Tinder Bot...');
|
console.log('🚀 Initializing Telegram Tinder Bot...');
|
||||||
|
|
||||||
|
// Инициализация сервиса локализации
|
||||||
|
await this.localizationService.initialize();
|
||||||
|
|
||||||
// Проверка подключения к базе данных
|
// Проверка подключения к базе данных
|
||||||
const dbConnected = await testConnection();
|
const dbConnected = await testConnection();
|
||||||
if (!dbConnected) {
|
if (!dbConnected) {
|
||||||
|
|||||||
212
src/controllers/translationController.ts
Normal file
212
src/controllers/translationController.ts
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import LocalizationService, { t } from '../services/localizationService';
|
||||||
|
import DeepSeekTranslationService from '../services/deepSeekTranslationService';
|
||||||
|
import { VipService } from '../services/vipService';
|
||||||
|
|
||||||
|
export class TranslationController {
|
||||||
|
private localizationService: LocalizationService;
|
||||||
|
private translationService: DeepSeekTranslationService;
|
||||||
|
private vipService: VipService;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.localizationService = LocalizationService.getInstance();
|
||||||
|
this.translationService = DeepSeekTranslationService.getInstance();
|
||||||
|
this.vipService = new VipService();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показать меню выбора языка
|
||||||
|
public getLanguageSelectionKeyboard() {
|
||||||
|
return {
|
||||||
|
inline_keyboard: [
|
||||||
|
[
|
||||||
|
{ text: '🇷🇺 Русский', callback_data: 'set_language_ru' },
|
||||||
|
{ text: '🇺🇸 English', callback_data: 'set_language_en' }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ text: '🇪🇸 Español', callback_data: 'set_language_es' },
|
||||||
|
{ text: '🇫🇷 Français', callback_data: 'set_language_fr' }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ text: '🇩🇪 Deutsch', callback_data: 'set_language_de' },
|
||||||
|
{ text: '🇮🇹 Italiano', callback_data: 'set_language_it' }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ text: '🇵🇹 Português', callback_data: 'set_language_pt' },
|
||||||
|
{ text: '🇨🇳 中文', callback_data: 'set_language_zh' }
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ text: '🇯🇵 日本語', callback_data: 'set_language_ja' },
|
||||||
|
{ text: '🇰🇷 한국어', callback_data: 'set_language_ko' }
|
||||||
|
],
|
||||||
|
[{ text: t('buttons.back'), callback_data: 'back_to_settings' }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработать установку языка
|
||||||
|
public async handleLanguageSelection(telegramId: number, languageCode: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
// Здесь должно быть обновление в базе данных
|
||||||
|
// await userService.updateUserLanguage(telegramId, languageCode);
|
||||||
|
|
||||||
|
this.localizationService.setLanguage(languageCode);
|
||||||
|
|
||||||
|
const languageNames: { [key: string]: string } = {
|
||||||
|
'ru': '🇷🇺 Русский',
|
||||||
|
'en': '🇺🇸 English',
|
||||||
|
'es': '🇪🇸 Español',
|
||||||
|
'fr': '🇫🇷 Français',
|
||||||
|
'de': '🇩🇪 Deutsch',
|
||||||
|
'it': '🇮🇹 Italiano',
|
||||||
|
'pt': '🇵🇹 Português',
|
||||||
|
'zh': '🇨🇳 中文',
|
||||||
|
'ja': '🇯🇵 日本語',
|
||||||
|
'ko': '🇰🇷 한국어'
|
||||||
|
};
|
||||||
|
|
||||||
|
return `✅ Язык интерфейса изменен на ${languageNames[languageCode] || languageCode}`;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting language:', error);
|
||||||
|
return t('errors.serverError');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получить кнопку перевода анкеты
|
||||||
|
public getTranslateProfileButton(telegramId: number, profileUserId: number) {
|
||||||
|
return {
|
||||||
|
inline_keyboard: [
|
||||||
|
[{ text: t('vip.translateProfile'), callback_data: `translate_profile_${profileUserId}` }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработать запрос на перевод анкеты
|
||||||
|
public async handleProfileTranslation(
|
||||||
|
telegramId: number,
|
||||||
|
profileUserId: number,
|
||||||
|
targetLanguage: string
|
||||||
|
): Promise<{ success: boolean; message: string; translatedProfile?: any }> {
|
||||||
|
try {
|
||||||
|
// Проверяем премиум статус
|
||||||
|
const isPremium = await this.vipService.checkPremiumStatus(telegramId.toString());
|
||||||
|
if (!isPremium) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: t('translation.premiumOnly')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем профиль для перевода
|
||||||
|
const profile = await this.getProfileForTranslation(profileUserId);
|
||||||
|
if (!profile) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: t('errors.profileNotFound')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Переводим профиль
|
||||||
|
const translatedProfile = await this.translateProfileData(profile, targetLanguage);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: t('translation.translated'),
|
||||||
|
translatedProfile
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Profile translation error:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: t('translation.error')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получить профиль для перевода (заглушка - нужна реализация)
|
||||||
|
private async getProfileForTranslation(userId: number): Promise<any> {
|
||||||
|
// TODO: Реализовать получение профиля из базы данных
|
||||||
|
// Это должно быть интегрировано с существующим ProfileService
|
||||||
|
return {
|
||||||
|
name: 'Sample Name',
|
||||||
|
bio: 'Sample bio text',
|
||||||
|
city: 'Sample City',
|
||||||
|
hobbies: 'Sample hobbies',
|
||||||
|
datingGoal: 'relationship'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перевести данные профиля
|
||||||
|
private async translateProfileData(profile: any, targetLanguage: string): Promise<any> {
|
||||||
|
const fieldsToTranslate = ['bio', 'hobbies'];
|
||||||
|
const translatedProfile = { ...profile };
|
||||||
|
|
||||||
|
for (const field of fieldsToTranslate) {
|
||||||
|
if (profile[field] && typeof profile[field] === 'string') {
|
||||||
|
try {
|
||||||
|
const sourceLanguage = this.translationService.detectLanguage(profile[field]);
|
||||||
|
|
||||||
|
// Пропускаем перевод, если исходный и целевой языки совпадают
|
||||||
|
if (sourceLanguage === targetLanguage) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const translation = await this.translationService.translateProfile({
|
||||||
|
text: profile[field],
|
||||||
|
targetLanguage,
|
||||||
|
sourceLanguage
|
||||||
|
});
|
||||||
|
|
||||||
|
translatedProfile[field] = translation.translatedText;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error translating field ${field}:`, error);
|
||||||
|
// Оставляем оригинальный текст при ошибке
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return translatedProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Форматировать переведенный профиль для отображения
|
||||||
|
public formatTranslatedProfile(profile: any, originalLanguage: string, targetLanguage: string): string {
|
||||||
|
const languageNames: { [key: string]: string } = {
|
||||||
|
'ru': '🇷🇺 Русский',
|
||||||
|
'en': '🇺🇸 English',
|
||||||
|
'es': '🇪🇸 Español',
|
||||||
|
'fr': '🇫🇷 Français',
|
||||||
|
'de': '🇩🇪 Deutsch',
|
||||||
|
'it': '🇮🇹 Italiano',
|
||||||
|
'pt': '🇵🇹 Português',
|
||||||
|
'zh': '🇨🇳 中文',
|
||||||
|
'ja': '🇯🇵 日本語',
|
||||||
|
'ko': '🇰🇷 한국어'
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = `🌐 ${t('translation.translated')}\n`;
|
||||||
|
text += `📝 ${originalLanguage} → ${targetLanguage}\n\n`;
|
||||||
|
|
||||||
|
text += `👤 ${t('profile.name')}: ${profile.name}\n`;
|
||||||
|
text += `📍 ${t('profile.city')}: ${profile.city}\n\n`;
|
||||||
|
|
||||||
|
if (profile.bio) {
|
||||||
|
text += `💭 ${t('profile.bio')}:\n${profile.bio}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.hobbies) {
|
||||||
|
text += `🎯 ${t('profile.hobbies')}:\n${profile.hobbies}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.datingGoal) {
|
||||||
|
text += `💕 ${t('profile.datingGoal')}: ${t(`profile.${profile.datingGoal}`)}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверить доступность сервиса перевода
|
||||||
|
public async checkTranslationServiceStatus(): Promise<boolean> {
|
||||||
|
return await this.translationService.checkServiceAvailability();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TranslationController;
|
||||||
291
src/controllers/vipController.ts
Normal file
291
src/controllers/vipController.ts
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
import TelegramBot, { InlineKeyboardMarkup } from 'node-telegram-bot-api';
|
||||||
|
import { VipService, VipSearchFilters } from '../services/vipService';
|
||||||
|
import { ProfileService } from '../services/profileService';
|
||||||
|
|
||||||
|
interface VipSearchState {
|
||||||
|
filters: VipSearchFilters;
|
||||||
|
currentStep: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VipController {
|
||||||
|
private bot: TelegramBot;
|
||||||
|
private vipService: VipService;
|
||||||
|
private profileService: ProfileService;
|
||||||
|
private vipSearchStates: Map<string, VipSearchState> = new Map();
|
||||||
|
|
||||||
|
constructor(bot: TelegramBot) {
|
||||||
|
this.bot = bot;
|
||||||
|
this.vipService = new VipService();
|
||||||
|
this.profileService = new ProfileService();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показать VIP поиск или информацию о премиум
|
||||||
|
async showVipSearch(chatId: number, telegramId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const premiumInfo = await this.vipService.checkPremiumStatus(telegramId);
|
||||||
|
|
||||||
|
if (!premiumInfo.isPremium) {
|
||||||
|
// Показываем информацию о премиум
|
||||||
|
await this.showPremiumInfo(chatId);
|
||||||
|
} else {
|
||||||
|
// Показываем VIP поиск
|
||||||
|
await this.showVipSearchMenu(chatId, telegramId, premiumInfo);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error showing VIP search:', error);
|
||||||
|
await this.bot.sendMessage(chatId, '❌ Ошибка при загрузке VIP поиска');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показать информацию о премиум подписке
|
||||||
|
private async showPremiumInfo(chatId: number): Promise<void> {
|
||||||
|
const premiumText = this.vipService.getPremiumFeatures();
|
||||||
|
|
||||||
|
const keyboard: InlineKeyboardMarkup = {
|
||||||
|
inline_keyboard: [
|
||||||
|
[{ text: '💎 Купить VIP', url: 'https://t.me/admin_bot' }],
|
||||||
|
[{ text: '🔙 Назад в меню', callback_data: 'main_menu' }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.bot.sendMessage(chatId, premiumText, {
|
||||||
|
reply_markup: keyboard
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показать меню VIP поиска
|
||||||
|
private async showVipSearchMenu(chatId: number, telegramId: string, premiumInfo: any): Promise<void> {
|
||||||
|
const daysText = premiumInfo.daysLeft ? ` (остался ${premiumInfo.daysLeft} дн.)` : '';
|
||||||
|
|
||||||
|
const text = `💎 VIP ПОИСК 💎\n\n` +
|
||||||
|
`✅ Премиум статус активен${daysText}\n\n` +
|
||||||
|
`🎯 Выберите тип поиска:`;
|
||||||
|
|
||||||
|
const keyboard: InlineKeyboardMarkup = {
|
||||||
|
inline_keyboard: [
|
||||||
|
[{ text: '🔍 Быстрый VIP поиск', callback_data: 'vip_quick_search' }],
|
||||||
|
[{ text: '⚙️ Расширенный поиск с фильтрами', callback_data: 'vip_advanced_search' }],
|
||||||
|
[{ text: '🎯 Поиск по целям знакомства', callback_data: 'vip_dating_goal_search' }],
|
||||||
|
[{ text: '🎨 Поиск по хобби', callback_data: 'vip_hobbies_search' }],
|
||||||
|
[{ text: '🔙 Назад в меню', callback_data: 'main_menu' }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.bot.sendMessage(chatId, text, {
|
||||||
|
reply_markup: keyboard
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Быстрый VIP поиск
|
||||||
|
async performQuickVipSearch(chatId: number, telegramId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const filters: VipSearchFilters = {
|
||||||
|
hasPhotos: true,
|
||||||
|
isOnline: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = await this.vipService.vipSearch(telegramId, filters);
|
||||||
|
await this.showSearchResults(chatId, telegramId, results, 'Быстрый VIP поиск');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in quick VIP search:', error);
|
||||||
|
await this.bot.sendMessage(chatId, '❌ Ошибка при выполнении поиска');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Начать настройку фильтров для расширенного поиска
|
||||||
|
async startAdvancedSearch(chatId: number, telegramId: string): Promise<void> {
|
||||||
|
const state: VipSearchState = {
|
||||||
|
filters: {},
|
||||||
|
currentStep: 'age_min'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.vipSearchStates.set(telegramId, state);
|
||||||
|
|
||||||
|
await this.bot.sendMessage(
|
||||||
|
chatId,
|
||||||
|
'⚙️ Расширенный VIP поиск\n\n' +
|
||||||
|
'🔢 Укажите минимальный возраст (18-65) или отправьте "-" чтобы пропустить:',
|
||||||
|
{ }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Поиск по целям знакомства
|
||||||
|
async showDatingGoalSearch(chatId: number, telegramId: string): Promise<void> {
|
||||||
|
const keyboard: InlineKeyboardMarkup = {
|
||||||
|
inline_keyboard: [
|
||||||
|
[{ text: '💕 Серьёзные отношения', callback_data: 'vip_goal_serious' }],
|
||||||
|
[{ text: '🎉 Лёгкие отношения', callback_data: 'vip_goal_casual' }],
|
||||||
|
[{ text: '👥 Дружба', callback_data: 'vip_goal_friends' }],
|
||||||
|
[{ text: '🔥 Одна ночь', callback_data: 'vip_goal_one_night' }],
|
||||||
|
[{ text: '😏 FWB', callback_data: 'vip_goal_fwb' }],
|
||||||
|
[{ text: '💎 Спонсорство', callback_data: 'vip_goal_sugar' }],
|
||||||
|
[{ text: '💍 Брак с переездом', callback_data: 'vip_goal_marriage_abroad' }],
|
||||||
|
[{ text: '💫 Полиамория', callback_data: 'vip_goal_polyamory' }],
|
||||||
|
[{ text: '🤷♀️ Пока не определился', callback_data: 'vip_goal_unsure' }],
|
||||||
|
[{ text: '🔙 Назад', callback_data: 'vip_search' }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.bot.sendMessage(
|
||||||
|
chatId,
|
||||||
|
'🎯 Поиск по целям знакомства\n\nВыберите цель:',
|
||||||
|
{
|
||||||
|
reply_markup: keyboard
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выполнить поиск по цели знакомства
|
||||||
|
async performDatingGoalSearch(chatId: number, telegramId: string, goal: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Используем значения из базы данных как есть
|
||||||
|
const filters: VipSearchFilters = {
|
||||||
|
datingGoal: goal,
|
||||||
|
hasPhotos: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = await this.vipService.vipSearch(telegramId, filters);
|
||||||
|
|
||||||
|
const goalNames: { [key: string]: string } = {
|
||||||
|
'serious': 'Серьёзные отношения',
|
||||||
|
'casual': 'Лёгкие отношения',
|
||||||
|
'friends': 'Дружба',
|
||||||
|
'one_night': 'Одна ночь',
|
||||||
|
'fwb': 'FWB',
|
||||||
|
'sugar': 'Спонсорство',
|
||||||
|
'marriage_abroad': 'Брак с переездом',
|
||||||
|
'polyamory': 'Полиамория',
|
||||||
|
'unsure': 'Пока не определился'
|
||||||
|
};
|
||||||
|
|
||||||
|
const goalName = goalNames[goal] || goal;
|
||||||
|
await this.showSearchResults(chatId, telegramId, results, `Поиск: ${goalName}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in dating goal search:', error);
|
||||||
|
await this.bot.sendMessage(chatId, '❌ Ошибка при выполнении поиска');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показать результаты поиска
|
||||||
|
private async showSearchResults(chatId: number, telegramId: string, results: any[], searchType: string): Promise<void> {
|
||||||
|
if (results.length === 0) {
|
||||||
|
const keyboard: InlineKeyboardMarkup = {
|
||||||
|
inline_keyboard: [
|
||||||
|
[{ text: '🔍 Новый поиск', callback_data: 'vip_search' }],
|
||||||
|
[{ text: '🔙 Главное меню', callback_data: 'main_menu' }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.bot.sendMessage(
|
||||||
|
chatId,
|
||||||
|
`😔 ${searchType}\n\n` +
|
||||||
|
'К сожалению, никого не найдено по вашим критериям.\n\n' +
|
||||||
|
'💡 Попробуйте изменить фильтры или выполнить обычный поиск.',
|
||||||
|
{
|
||||||
|
reply_markup: keyboard,
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.bot.sendMessage(
|
||||||
|
chatId,
|
||||||
|
`🎉 ${searchType}\n\n` +
|
||||||
|
`Найдено: ${results.length} ${this.getCountText(results.length)}\n\n` +
|
||||||
|
'Начинаем просмотр профилей...',
|
||||||
|
{ }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Показываем первый профиль из результатов
|
||||||
|
const firstProfile = results[0];
|
||||||
|
await this.showVipSearchProfile(chatId, telegramId, firstProfile, results, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показать профиль из VIP поиска
|
||||||
|
private async showVipSearchProfile(chatId: number, telegramId: string, profile: any, allResults: any[], currentIndex: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
let profileText = `💎 VIP Поиск (${currentIndex + 1}/${allResults.length})\n\n`;
|
||||||
|
profileText += `👤 ${profile.name}, ${profile.age}\n`;
|
||||||
|
profileText += `📍 ${profile.city || 'Не указан'}\n`;
|
||||||
|
|
||||||
|
if (profile.dating_goal) {
|
||||||
|
const goalText = this.getDatingGoalText(profile.dating_goal);
|
||||||
|
profileText += `🎯 ${goalText}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.bio) {
|
||||||
|
profileText += `\n📝 ${profile.bio}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.is_online) {
|
||||||
|
profileText += `\n🟢 Онлайн\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyboard: InlineKeyboardMarkup = {
|
||||||
|
inline_keyboard: [
|
||||||
|
[
|
||||||
|
{ text: '❤️', callback_data: `vip_like_${profile.telegram_id}` },
|
||||||
|
{ text: '⭐', callback_data: `vip_superlike_${profile.telegram_id}` },
|
||||||
|
{ text: '👎', callback_data: `vip_dislike_${profile.telegram_id}` }
|
||||||
|
],
|
||||||
|
[{ text: '👤 Полный профиль', callback_data: `view_profile_${profile.user_id}` }],
|
||||||
|
[
|
||||||
|
{ text: '⬅️ Предыдущий', callback_data: `vip_prev_${currentIndex}` },
|
||||||
|
{ text: '➡️ Следующий', callback_data: `vip_next_${currentIndex}` }
|
||||||
|
],
|
||||||
|
[{ text: '🔍 Новый поиск', callback_data: 'vip_search' }],
|
||||||
|
[{ text: '🔙 Главное меню', callback_data: 'main_menu' }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Проверяем есть ли фотографии
|
||||||
|
if (profile.photos && Array.isArray(profile.photos) && profile.photos.length > 0) {
|
||||||
|
await this.bot.sendPhoto(chatId, profile.photos[0], {
|
||||||
|
caption: profileText,
|
||||||
|
reply_markup: keyboard,
|
||||||
|
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await this.bot.sendMessage(chatId, profileText, {
|
||||||
|
reply_markup: keyboard,
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем результаты поиска для навигации
|
||||||
|
// Можно сохранить в Redis или временной переменной
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error showing VIP search profile:', error);
|
||||||
|
await this.bot.sendMessage(chatId, '❌ Ошибка при показе профиля');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCountText(count: number): string {
|
||||||
|
const lastDigit = count % 10;
|
||||||
|
const lastTwoDigits = count % 100;
|
||||||
|
|
||||||
|
if (lastTwoDigits >= 11 && lastTwoDigits <= 14) {
|
||||||
|
return 'пользователей';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (lastDigit) {
|
||||||
|
case 1: return 'пользователь';
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4: return 'пользователя';
|
||||||
|
default: return 'пользователей';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDatingGoalText(goal: string): string {
|
||||||
|
const goals: { [key: string]: string } = {
|
||||||
|
'serious_relationship': 'Серьезные отношения',
|
||||||
|
'friendship': 'Общение и дружба',
|
||||||
|
'fun': 'Развлечения',
|
||||||
|
'networking': 'Деловые знакомства'
|
||||||
|
};
|
||||||
|
return goals[goal] || 'Не указано';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import { Pool, PoolConfig } from 'pg';
|
|||||||
// Конфигурация пула соединений PostgreSQL
|
// Конфигурация пула соединений PostgreSQL
|
||||||
const poolConfig: PoolConfig = {
|
const poolConfig: PoolConfig = {
|
||||||
host: process.env.DB_HOST || 'localhost',
|
host: process.env.DB_HOST || 'localhost',
|
||||||
port: parseInt(process.env.DB_PORT || '5432'),
|
port: parseInt(process.env.DB_PORT || '5433'),
|
||||||
database: process.env.DB_NAME || 'telegram_tinder_bot',
|
database: process.env.DB_NAME || 'telegram_tinder_bot',
|
||||||
user: process.env.DB_USERNAME || 'postgres',
|
user: process.env.DB_USERNAME || 'postgres',
|
||||||
...(process.env.DB_PASSWORD && { password: process.env.DB_PASSWORD }),
|
...(process.env.DB_PASSWORD && { password: process.env.DB_PASSWORD }),
|
||||||
|
|||||||
14
src/database/migrations/add_language_support.sql
Normal file
14
src/database/migrations/add_language_support.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- Добавляем поле языка пользователя в таблицу users
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN language VARCHAR(5) DEFAULT 'ru';
|
||||||
|
|
||||||
|
-- Создаем индекс для оптимизации запросов по языку
|
||||||
|
CREATE INDEX idx_users_language ON users(language);
|
||||||
|
|
||||||
|
-- Добавляем ограничение на поддерживаемые языки
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD CONSTRAINT check_users_language
|
||||||
|
CHECK (language IN ('ru', 'en', 'es', 'fr', 'de', 'it', 'pt', 'zh', 'ja', 'ko'));
|
||||||
|
|
||||||
|
-- Обновляем существующих пользователей
|
||||||
|
UPDATE users SET language = 'ru' WHERE language IS NULL;
|
||||||
10
src/database/migrations/add_premium_field.sql
Normal file
10
src/database/migrations/add_premium_field.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
-- Добавление поля premium для VIP функций
|
||||||
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS premium BOOLEAN DEFAULT FALSE;
|
||||||
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS premium_expires_at TIMESTAMP WITH TIME ZONE DEFAULT NULL;
|
||||||
|
|
||||||
|
-- Индекс для быстрого поиска premium пользователей
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_users_premium ON users(premium, premium_expires_at);
|
||||||
|
|
||||||
|
-- Комментарии
|
||||||
|
COMMENT ON COLUMN users.premium IS 'VIP статус пользователя';
|
||||||
|
COMMENT ON COLUMN users.premium_expires_at IS 'Дата окончания VIP статуса';
|
||||||
@@ -6,6 +6,10 @@ import { Profile } from '../models/Profile';
|
|||||||
import { MessageHandlers } from './messageHandlers';
|
import { MessageHandlers } from './messageHandlers';
|
||||||
import { ProfileEditController } from '../controllers/profileEditController';
|
import { ProfileEditController } from '../controllers/profileEditController';
|
||||||
import { EnhancedChatHandlers } from './enhancedChatHandlers';
|
import { EnhancedChatHandlers } from './enhancedChatHandlers';
|
||||||
|
import { VipController } from '../controllers/vipController';
|
||||||
|
import { VipService } from '../services/vipService';
|
||||||
|
import { TranslationController } from '../controllers/translationController';
|
||||||
|
import { t } from '../services/localizationService';
|
||||||
|
|
||||||
export class CallbackHandlers {
|
export class CallbackHandlers {
|
||||||
private bot: TelegramBot;
|
private bot: TelegramBot;
|
||||||
@@ -15,6 +19,9 @@ export class CallbackHandlers {
|
|||||||
private messageHandlers: MessageHandlers;
|
private messageHandlers: MessageHandlers;
|
||||||
private profileEditController: ProfileEditController;
|
private profileEditController: ProfileEditController;
|
||||||
private enhancedChatHandlers: EnhancedChatHandlers;
|
private enhancedChatHandlers: EnhancedChatHandlers;
|
||||||
|
private vipController: VipController;
|
||||||
|
private vipService: VipService;
|
||||||
|
private translationController: TranslationController;
|
||||||
|
|
||||||
constructor(bot: TelegramBot, messageHandlers: MessageHandlers) {
|
constructor(bot: TelegramBot, messageHandlers: MessageHandlers) {
|
||||||
this.bot = bot;
|
this.bot = bot;
|
||||||
@@ -24,6 +31,9 @@ export class CallbackHandlers {
|
|||||||
this.messageHandlers = messageHandlers;
|
this.messageHandlers = messageHandlers;
|
||||||
this.profileEditController = new ProfileEditController(this.profileService);
|
this.profileEditController = new ProfileEditController(this.profileService);
|
||||||
this.enhancedChatHandlers = new EnhancedChatHandlers(bot);
|
this.enhancedChatHandlers = new EnhancedChatHandlers(bot);
|
||||||
|
this.vipController = new VipController(bot);
|
||||||
|
this.vipService = new VipService();
|
||||||
|
this.translationController = new TranslationController();
|
||||||
}
|
}
|
||||||
|
|
||||||
register(): void {
|
register(): void {
|
||||||
@@ -211,7 +221,43 @@ export class CallbackHandlers {
|
|||||||
} else if (data === 'back_to_browsing') {
|
} else if (data === 'back_to_browsing') {
|
||||||
await this.handleStartBrowsing(chatId, telegramId);
|
await this.handleStartBrowsing(chatId, telegramId);
|
||||||
} else if (data === 'get_vip') {
|
} else if (data === 'get_vip') {
|
||||||
await this.handleGetVip(chatId, telegramId);
|
await this.vipController.showVipSearch(chatId, telegramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIP функции
|
||||||
|
else if (data === 'vip_search') {
|
||||||
|
await this.vipController.showVipSearch(chatId, telegramId);
|
||||||
|
} else if (data === 'vip_quick_search') {
|
||||||
|
await this.vipController.performQuickVipSearch(chatId, telegramId);
|
||||||
|
} else if (data === 'vip_advanced_search') {
|
||||||
|
await this.vipController.startAdvancedSearch(chatId, telegramId);
|
||||||
|
} else if (data === 'vip_dating_goal_search') {
|
||||||
|
await this.vipController.showDatingGoalSearch(chatId, telegramId);
|
||||||
|
} else if (data.startsWith('vip_goal_')) {
|
||||||
|
const goal = data.replace('vip_goal_', '');
|
||||||
|
await this.vipController.performDatingGoalSearch(chatId, telegramId, goal);
|
||||||
|
} else if (data.startsWith('vip_like_')) {
|
||||||
|
const targetTelegramId = data.replace('vip_like_', '');
|
||||||
|
await this.handleVipLike(chatId, telegramId, targetTelegramId);
|
||||||
|
} else if (data.startsWith('vip_superlike_')) {
|
||||||
|
const targetTelegramId = data.replace('vip_superlike_', '');
|
||||||
|
await this.handleVipSuperlike(chatId, telegramId, targetTelegramId);
|
||||||
|
} else if (data.startsWith('vip_dislike_')) {
|
||||||
|
const targetTelegramId = data.replace('vip_dislike_', '');
|
||||||
|
await this.handleVipDislike(chatId, telegramId, targetTelegramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Настройки языка и переводы
|
||||||
|
else if (data === 'language_settings') {
|
||||||
|
await this.handleLanguageSettings(chatId, telegramId);
|
||||||
|
} else if (data.startsWith('set_language_')) {
|
||||||
|
const languageCode = data.replace('set_language_', '');
|
||||||
|
await this.handleSetLanguage(chatId, telegramId, languageCode);
|
||||||
|
} else if (data.startsWith('translate_profile_')) {
|
||||||
|
const profileUserId = parseInt(data.replace('translate_profile_', ''));
|
||||||
|
await this.handleTranslateProfile(chatId, telegramId, profileUserId);
|
||||||
|
} else if (data === 'back_to_settings') {
|
||||||
|
await this.handleSettings(chatId, telegramId);
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
@@ -692,8 +738,8 @@ export class CallbackHandlers {
|
|||||||
{ text: '🔔 Уведомления', callback_data: 'notification_settings' }
|
{ text: '🔔 Уведомления', callback_data: 'notification_settings' }
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ text: '<EFBFBD> Статистика', callback_data: 'view_stats' },
|
{ text: '🌐 Язык интерфейса', callback_data: 'language_settings' },
|
||||||
{ text: '👀 Кто смотрел', callback_data: 'view_profile_viewers' }
|
{ text: '📊 Статистика', callback_data: 'view_stats' }
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ text: '<27>🚫 Скрыть профиль', callback_data: 'hide_profile' },
|
{ text: '<27>🚫 Скрыть профиль', callback_data: 'hide_profile' },
|
||||||
@@ -1724,20 +1770,30 @@ export class CallbackHandlers {
|
|||||||
const profile = await this.profileService.getProfileByTelegramId(telegramId);
|
const profile = await this.profileService.getProfileByTelegramId(telegramId);
|
||||||
|
|
||||||
if (profile) {
|
if (profile) {
|
||||||
const keyboard: InlineKeyboardMarkup = {
|
// Проверяем премиум статус
|
||||||
inline_keyboard: [
|
const premiumInfo = await this.vipService.checkPremiumStatus(telegramId);
|
||||||
[
|
|
||||||
{ text: '👤 Мой профиль', callback_data: 'view_my_profile' },
|
let keyboardRows = [
|
||||||
{ text: '🔍 Просмотр анкет', callback_data: 'start_browsing' }
|
[
|
||||||
],
|
{ text: '👤 Мой профиль', callback_data: 'view_my_profile' },
|
||||||
[
|
{ text: '🔍 Просмотр анкет', callback_data: 'start_browsing' }
|
||||||
{ text: '💕 Мои матчи', callback_data: 'view_matches' },
|
],
|
||||||
{ text: '⭐ VIP поиск', callback_data: 'vip_search' }
|
[
|
||||||
],
|
{ text: '💕 Мои матчи', callback_data: 'view_matches' }
|
||||||
[
|
|
||||||
{ text: '⚙️ Настройки', callback_data: 'settings' }
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Добавляем кнопку VIP поиска если есть премиум, или кнопку "Получить VIP" если нет
|
||||||
|
if (premiumInfo && premiumInfo.isPremium) {
|
||||||
|
keyboardRows[1].push({ text: '⭐ VIP поиск', callback_data: 'vip_search' });
|
||||||
|
} else {
|
||||||
|
keyboardRows[1].push({ text: '💎 Получить VIP', callback_data: 'get_vip' });
|
||||||
|
}
|
||||||
|
|
||||||
|
keyboardRows.push([{ text: '⚙️ Настройки', callback_data: 'settings' }]);
|
||||||
|
|
||||||
|
const keyboard: InlineKeyboardMarkup = {
|
||||||
|
inline_keyboard: keyboardRows
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.sendMessage(
|
await this.bot.sendMessage(
|
||||||
@@ -1886,4 +1942,155 @@ export class CallbackHandlers {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VIP лайк
|
||||||
|
async handleVipLike(chatId: number, telegramId: string, targetTelegramId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Получаем user_id по telegram_id для совместимости с существующей логикой
|
||||||
|
const targetUserId = await this.profileService.getUserIdByTelegramId(targetTelegramId);
|
||||||
|
if (!targetUserId) {
|
||||||
|
throw new Error('Target user not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.matchingService.performSwipe(telegramId, targetTelegramId, 'like');
|
||||||
|
|
||||||
|
if (result.isMatch) {
|
||||||
|
// Это матч!
|
||||||
|
const targetProfile = await this.profileService.getProfileByUserId(targetUserId);
|
||||||
|
|
||||||
|
const keyboard: InlineKeyboardMarkup = {
|
||||||
|
inline_keyboard: [
|
||||||
|
[
|
||||||
|
{ text: '💬 Написать сообщение', callback_data: 'chat_' + targetUserId },
|
||||||
|
{ text: '📱 Нативный чат', callback_data: 'open_native_chat_' + result.match?.id }
|
||||||
|
],
|
||||||
|
[{ text: '🔍 Продолжить VIP поиск', callback_data: 'vip_search' }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.bot.sendMessage(
|
||||||
|
chatId,
|
||||||
|
'🎉 ЭТО МАТЧ! 💕\n\n' +
|
||||||
|
'Вы понравились друг другу с ' + (targetProfile?.name || 'этим пользователем') + '!\n\n' +
|
||||||
|
'Теперь вы можете начать общение!',
|
||||||
|
{ reply_markup: keyboard }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await this.bot.sendMessage(chatId, '👍 Лайк отправлен! Продолжайте VIP поиск.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await this.bot.sendMessage(chatId, '❌ Ошибка при отправке лайка');
|
||||||
|
console.error('VIP Like error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIP супер-лайк
|
||||||
|
async handleVipSuperlike(chatId: number, telegramId: string, targetTelegramId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const targetUserId = await this.profileService.getUserIdByTelegramId(targetTelegramId);
|
||||||
|
if (!targetUserId) {
|
||||||
|
throw new Error('Target user not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.matchingService.performSwipe(telegramId, targetTelegramId, 'superlike');
|
||||||
|
|
||||||
|
if (result.isMatch) {
|
||||||
|
const targetProfile = await this.profileService.getProfileByUserId(targetUserId);
|
||||||
|
|
||||||
|
const keyboard: InlineKeyboardMarkup = {
|
||||||
|
inline_keyboard: [
|
||||||
|
[
|
||||||
|
{ text: '💬 Написать сообщение', callback_data: 'chat_' + targetUserId },
|
||||||
|
{ text: '📱 Нативный чат', callback_data: 'open_native_chat_' + result.match?.id }
|
||||||
|
],
|
||||||
|
[{ text: '🔍 Продолжить VIP поиск', callback_data: 'vip_search' }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.bot.sendMessage(
|
||||||
|
chatId,
|
||||||
|
'⭐ СУПЕР МАТЧ! ⭐\n\n' +
|
||||||
|
'Ваш супер-лайк привел к матчу с ' + (targetProfile?.name || 'этим пользователем') + '!\n\n' +
|
||||||
|
'Начните общение прямо сейчас!',
|
||||||
|
{ reply_markup: keyboard }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await this.bot.sendMessage(chatId, '⭐ Супер-лайк отправлен! Это повышает ваши шансы.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await this.bot.sendMessage(chatId, '❌ Ошибка при отправке супер-лайка');
|
||||||
|
console.error('VIP Superlike error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIP дизлайк
|
||||||
|
async handleVipDislike(chatId: number, telegramId: string, targetTelegramId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.matchingService.performSwipe(telegramId, targetTelegramId, 'pass');
|
||||||
|
await this.bot.sendMessage(chatId, '👎 Профиль пропущен. Продолжайте VIP поиск.');
|
||||||
|
} catch (error) {
|
||||||
|
await this.bot.sendMessage(chatId, '❌ Ошибка при выполнении действия');
|
||||||
|
console.error('VIP Dislike error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчики языковых настроек
|
||||||
|
async handleLanguageSettings(chatId: number, telegramId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const keyboard = this.translationController.getLanguageSelectionKeyboard();
|
||||||
|
await this.bot.sendMessage(
|
||||||
|
chatId,
|
||||||
|
`🌐 ${t('commands.settings')} - Выбор языка\n\nВыберите язык интерфейса:`,
|
||||||
|
{ reply_markup: keyboard }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Language settings error:', error);
|
||||||
|
await this.bot.sendMessage(chatId, t('errors.serverError'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleSetLanguage(chatId: number, telegramId: string, languageCode: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const result = await this.translationController.handleLanguageSelection(parseInt(telegramId), languageCode);
|
||||||
|
await this.bot.sendMessage(chatId, result);
|
||||||
|
|
||||||
|
// Показать обновленное меню настроек
|
||||||
|
setTimeout(() => {
|
||||||
|
this.handleSettings(chatId, telegramId);
|
||||||
|
}, 1000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Set language error:', error);
|
||||||
|
await this.bot.sendMessage(chatId, t('errors.serverError'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleTranslateProfile(chatId: number, telegramId: string, profileUserId: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Показать индикатор загрузки
|
||||||
|
await this.bot.sendMessage(chatId, t('translation.translating'));
|
||||||
|
|
||||||
|
// Получить текущий язык пользователя
|
||||||
|
const userLanguage = 'ru'; // TODO: получить из базы данных
|
||||||
|
|
||||||
|
const result = await this.translationController.handleProfileTranslation(
|
||||||
|
parseInt(telegramId),
|
||||||
|
profileUserId,
|
||||||
|
userLanguage
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.success && result.translatedProfile) {
|
||||||
|
const formattedProfile = this.translationController.formatTranslatedProfile(
|
||||||
|
result.translatedProfile,
|
||||||
|
'auto',
|
||||||
|
userLanguage
|
||||||
|
);
|
||||||
|
await this.bot.sendMessage(chatId, formattedProfile);
|
||||||
|
} else {
|
||||||
|
await this.bot.sendMessage(chatId, result.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Translate profile error:', error);
|
||||||
|
await this.bot.sendMessage(chatId, t('translation.error'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import TelegramBot, { Message, InlineKeyboardMarkup } from 'node-telegram-bot-ap
|
|||||||
import { ProfileService } from '../services/profileService';
|
import { ProfileService } from '../services/profileService';
|
||||||
import { MatchingService } from '../services/matchingService';
|
import { MatchingService } from '../services/matchingService';
|
||||||
import { Profile } from '../models/Profile';
|
import { Profile } from '../models/Profile';
|
||||||
|
import { getUserTranslation } from '../services/localizationService';
|
||||||
|
|
||||||
export class CommandHandlers {
|
export class CommandHandlers {
|
||||||
private bot: TelegramBot;
|
private bot: TelegramBot;
|
||||||
@@ -104,15 +105,18 @@ export class CommandHandlers {
|
|||||||
const profile = await this.profileService.getProfileByTelegramId(userId);
|
const profile = await this.profileService.getProfileByTelegramId(userId);
|
||||||
|
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
|
const createProfileText = await getUserTranslation(userId, 'profile.create');
|
||||||
|
const noProfileText = await getUserTranslation(userId, 'profile.noProfile');
|
||||||
|
|
||||||
const keyboard: InlineKeyboardMarkup = {
|
const keyboard: InlineKeyboardMarkup = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
[{ text: '🚀 Создать профиль', callback_data: 'create_profile' }]
|
[{ text: createProfileText, callback_data: 'create_profile' }]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.sendMessage(
|
await this.bot.sendMessage(
|
||||||
msg.chat.id,
|
msg.chat.id,
|
||||||
'❌ У вас пока нет профиля.\nСоздайте его для начала использования бота!',
|
noProfileText,
|
||||||
{ reply_markup: keyboard }
|
{ reply_markup: keyboard }
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -129,9 +133,11 @@ export class CommandHandlers {
|
|||||||
const profile = await this.profileService.getProfileByTelegramId(userId);
|
const profile = await this.profileService.getProfileByTelegramId(userId);
|
||||||
|
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
|
const createFirstText = await getUserTranslation(userId, 'profile.createFirst');
|
||||||
|
|
||||||
await this.bot.sendMessage(
|
await this.bot.sendMessage(
|
||||||
msg.chat.id,
|
msg.chat.id,
|
||||||
'❌ Сначала создайте профиль!\nИспользуйте команду /start'
|
createFirstText
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
101
src/locales/de.json
Normal file
101
src/locales/de.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "Willkommen beim Telegram Tinder Bot! 💕",
|
||||||
|
"description": "Finde deine Seelenverwandte direkt hier!",
|
||||||
|
"getStarted": "Loslegen"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "Profil Erstellen",
|
||||||
|
"edit": "Profil Bearbeiten",
|
||||||
|
"view": "Profil Ansehen",
|
||||||
|
"name": "Name",
|
||||||
|
"age": "Alter",
|
||||||
|
"city": "Stadt",
|
||||||
|
"bio": "Über mich",
|
||||||
|
"photos": "Fotos",
|
||||||
|
"gender": "Geschlecht",
|
||||||
|
"lookingFor": "Suche nach",
|
||||||
|
"datingGoal": "Dating-Ziel",
|
||||||
|
"hobbies": "Hobbys",
|
||||||
|
"lifestyle": "Lebensstil",
|
||||||
|
"male": "Männlich",
|
||||||
|
"female": "Weiblich",
|
||||||
|
"both": "Beide",
|
||||||
|
"relationship": "Beziehung",
|
||||||
|
"friendship": "Freundschaft",
|
||||||
|
"dating": "Dating",
|
||||||
|
"hookup": "Abenteuer",
|
||||||
|
"marriage": "Ehe",
|
||||||
|
"networking": "Networking",
|
||||||
|
"travel": "Reisen",
|
||||||
|
"business": "Geschäft",
|
||||||
|
"other": "Andere"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "Profile Durchsuchen",
|
||||||
|
"noProfiles": "Keine weiteren Profile! Versuche es später erneut.",
|
||||||
|
"like": "❤️ Gefällt mir",
|
||||||
|
"dislike": "👎 Überspringen",
|
||||||
|
"superLike": "⭐ Super Like",
|
||||||
|
"match": "Es ist ein Match! 🎉"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "VIP-Suche",
|
||||||
|
"premiumRequired": "Diese Funktion ist nur für Premium-Nutzer verfügbar",
|
||||||
|
"filters": "Filter",
|
||||||
|
"ageRange": "Altersbereich",
|
||||||
|
"cityFilter": "Stadt",
|
||||||
|
"datingGoalFilter": "Dating-Ziel",
|
||||||
|
"hobbiesFilter": "Hobbys",
|
||||||
|
"lifestyleFilter": "Lebensstil",
|
||||||
|
"applyFilters": "Filter Anwenden",
|
||||||
|
"clearFilters": "Filter Löschen",
|
||||||
|
"noResults": "Keine Profile mit deinen Filtern gefunden",
|
||||||
|
"translateProfile": "🌐 Profil Übersetzen"
|
||||||
|
},
|
||||||
|
"premium": {
|
||||||
|
"title": "Premium-Abonnement",
|
||||||
|
"features": "Premium-Features:",
|
||||||
|
"vipSearch": "• VIP-Suche mit Filtern",
|
||||||
|
"profileTranslation": "• Profilübersetzung in deine Sprache",
|
||||||
|
"unlimitedLikes": "• Unbegrenzte Likes",
|
||||||
|
"superLikes": "• Zusätzliche Super-Likes",
|
||||||
|
"price": "Preis: 4,99€/Monat",
|
||||||
|
"activate": "Premium Aktivieren"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"translating": "Profil wird übersetzt...",
|
||||||
|
"translated": "Profil übersetzt:",
|
||||||
|
"error": "Übersetzungsfehler. Bitte versuche es später erneut.",
|
||||||
|
"premiumOnly": "Übersetzung ist nur für Premium-Nutzer verfügbar"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "Hauptmenü",
|
||||||
|
"profile": "Mein Profil",
|
||||||
|
"search": "Durchsuchen",
|
||||||
|
"vip": "VIP-Suche",
|
||||||
|
"matches": "Matches",
|
||||||
|
"premium": "Premium",
|
||||||
|
"settings": "Einstellungen",
|
||||||
|
"help": "Hilfe"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« Zurück",
|
||||||
|
"next": "Weiter »",
|
||||||
|
"save": "Speichern",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"confirm": "Bestätigen",
|
||||||
|
"edit": "Bearbeiten",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"yes": "Ja",
|
||||||
|
"no": "Nein"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Profil nicht gefunden",
|
||||||
|
"profileIncomplete": "Bitte vervollständige dein Profil",
|
||||||
|
"ageInvalid": "Bitte gib ein gültiges Alter ein (18-100)",
|
||||||
|
"photoRequired": "Bitte füge mindestens ein Foto hinzu",
|
||||||
|
"networkError": "Netzwerkfehler. Bitte versuche es später erneut.",
|
||||||
|
"serverError": "Serverfehler. Bitte versuche es später erneut."
|
||||||
|
}
|
||||||
|
}
|
||||||
113
src/locales/en.json
Normal file
113
src/locales/en.json
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "Welcome to Telegram Tinder Bot! 💕",
|
||||||
|
"description": "Find your soulmate right here!",
|
||||||
|
"getStarted": "Get Started"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "Create Profile",
|
||||||
|
"edit": "✏️ Edit",
|
||||||
|
"view": "View Profile",
|
||||||
|
"name": "Name",
|
||||||
|
"age": "Age",
|
||||||
|
"city": "City",
|
||||||
|
"bio": "About",
|
||||||
|
"photos": "📸 Photos",
|
||||||
|
"gender": "Gender",
|
||||||
|
"lookingFor": "Looking for",
|
||||||
|
"datingGoal": "Dating Goal",
|
||||||
|
"hobbies": "Hobbies",
|
||||||
|
"lifestyle": "Lifestyle",
|
||||||
|
"male": "Male",
|
||||||
|
"female": "Female",
|
||||||
|
"both": "Both",
|
||||||
|
"relationship": "Relationship",
|
||||||
|
"friendship": "Friendship",
|
||||||
|
"dating": "Dating",
|
||||||
|
"hookup": "Hookup",
|
||||||
|
"marriage": "Marriage",
|
||||||
|
"networking": "Networking",
|
||||||
|
"travel": "Travel",
|
||||||
|
"business": "Business",
|
||||||
|
"other": "Other",
|
||||||
|
"cityNotSpecified": "Not specified",
|
||||||
|
"bioNotSpecified": "No description provided",
|
||||||
|
"interests": "Interests",
|
||||||
|
"startSearch": "🔍 Start Search",
|
||||||
|
"noProfile": "❌ You don't have a profile yet.\nCreate one to start using the bot!",
|
||||||
|
"createFirst": "❌ Create a profile first!\nUse /start command"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "Browse Profiles",
|
||||||
|
"noProfiles": "No more profiles! Try again later.",
|
||||||
|
"like": "❤️ Like",
|
||||||
|
"dislike": "👎 Pass",
|
||||||
|
"superLike": "⭐ Super Like",
|
||||||
|
"match": "It's a match! 🎉"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "VIP Search",
|
||||||
|
"premiumRequired": "This feature is available for premium users only",
|
||||||
|
"filters": "Filters",
|
||||||
|
"ageRange": "Age Range",
|
||||||
|
"cityFilter": "City",
|
||||||
|
"datingGoalFilter": "Dating Goal",
|
||||||
|
"hobbiesFilter": "Hobbies",
|
||||||
|
"lifestyleFilter": "Lifestyle",
|
||||||
|
"applyFilters": "Apply Filters",
|
||||||
|
"clearFilters": "Clear Filters",
|
||||||
|
"noResults": "No profiles found with your filters",
|
||||||
|
"translateProfile": "🌐 Translate Profile"
|
||||||
|
},
|
||||||
|
"premium": {
|
||||||
|
"title": "Premium Subscription",
|
||||||
|
"features": "Premium features:",
|
||||||
|
"vipSearch": "• VIP search with filters",
|
||||||
|
"profileTranslation": "• Profile translation to your language",
|
||||||
|
"unlimitedLikes": "• Unlimited likes",
|
||||||
|
"superLikes": "• Extra super likes",
|
||||||
|
"price": "Price: $4.99/month",
|
||||||
|
"activate": "Activate Premium"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"translating": "Translating profile...",
|
||||||
|
"translated": "Profile translated:",
|
||||||
|
"error": "Translation error. Please try again later.",
|
||||||
|
"premiumOnly": "Translation is available for premium users only"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "Main Menu",
|
||||||
|
"profile": "My Profile",
|
||||||
|
"search": "Browse",
|
||||||
|
"vip": "VIP Search",
|
||||||
|
"matches": "Matches",
|
||||||
|
"premium": "Premium",
|
||||||
|
"settings": "Settings",
|
||||||
|
"help": "Help"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« Back",
|
||||||
|
"next": "Next »",
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Profile not found",
|
||||||
|
"profileIncomplete": "Please complete your profile",
|
||||||
|
"ageInvalid": "Please enter a valid age (18-100)",
|
||||||
|
"photoRequired": "Please add at least one photo",
|
||||||
|
"networkError": "Network error. Please try again later.",
|
||||||
|
"serverError": "Server error. Please try again later."
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"back": "👈 Back"
|
||||||
|
},
|
||||||
|
"matches": {
|
||||||
|
"noMatches": "✨ You don't have any matches yet.\n\n🔍 Try browsing more profiles!\nUse /browse to search."
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/locales/en_fixed.json
Normal file
94
src/locales/en_fixed.json
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"commands": {
|
||||||
|
"start": "🏠 Main menu",
|
||||||
|
"help": "ℹ️ Help",
|
||||||
|
"profile": "👤 My profile",
|
||||||
|
"search": "🔍 Browse profiles",
|
||||||
|
"matches": "💕 Matches",
|
||||||
|
"premium": "⭐ Premium",
|
||||||
|
"settings": "⚙️ Settings"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"main": "🏠 Main menu",
|
||||||
|
"back": "👈 Back",
|
||||||
|
"profile": "👤 Profile",
|
||||||
|
"search": "🔍 Browse",
|
||||||
|
"matches": "💕 Matches",
|
||||||
|
"premium": "⭐ Premium",
|
||||||
|
"settings": "⚙️ Settings"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"newUser": "Welcome to Telegram Tinder Bot! 💕\\n\\nHere you can find interesting people for communication and dating.\\n\\nTo get started, create your profile!",
|
||||||
|
"existingUser": "Welcome back! 👋\\n\\nChoose an action:",
|
||||||
|
"createProfile": "🚀 Create profile"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"title": "📋 How to use the bot:",
|
||||||
|
"step1": "1️⃣ Create profile",
|
||||||
|
"step1Desc": " • Enter name, age, city\\n • Add description\\n • Upload photo",
|
||||||
|
"step2": "2️⃣ Browse profiles",
|
||||||
|
"step2Desc": " • Swipe through other users' profiles\\n • Like (❤️) or dislike (👎)",
|
||||||
|
"step3": "3️⃣ Get match",
|
||||||
|
"step3Desc": " • When two people like each other\\n • Chat becomes available",
|
||||||
|
"step4": "4️⃣ Communication",
|
||||||
|
"step4Desc": " • Find common interests\\n • Arrange meetings",
|
||||||
|
"tipsTitle": "💡 Tips:",
|
||||||
|
"tips": "• Use quality photos\\n• Write interesting description\\n• Be polite in communication",
|
||||||
|
"createProfile": "🚀 Create profile"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "⚙️ Settings",
|
||||||
|
"language": "🌐 Interface language",
|
||||||
|
"ageRange": "📅 Age range",
|
||||||
|
"showAge": "🎂 Show age",
|
||||||
|
"showCity": "📍 Show city",
|
||||||
|
"notifications": "🔔 Notifications",
|
||||||
|
"privacy": "🔒 Privacy",
|
||||||
|
"back": "👈 Back"
|
||||||
|
},
|
||||||
|
"languages": {
|
||||||
|
"ru": "🇷🇺 Русский",
|
||||||
|
"en": "🇺🇸 English",
|
||||||
|
"es": "🇪🇸 Español",
|
||||||
|
"fr": "🇫🇷 Français",
|
||||||
|
"de": "🇩🇪 Deutsch",
|
||||||
|
"it": "🇮🇹 Italiano",
|
||||||
|
"pt": "🇵🇹 Português",
|
||||||
|
"zh": "🇨🇳 中文",
|
||||||
|
"ja": "🇯🇵 日本語",
|
||||||
|
"ko": "🇰🇷 한국어",
|
||||||
|
"uz": "🇺🇿 O'zbekcha",
|
||||||
|
"kk": "🇰🇿 Қазақша"
|
||||||
|
},
|
||||||
|
"howItWorks": {
|
||||||
|
"title": "🤔 How does it work?",
|
||||||
|
"step1": "1️⃣ Create profile",
|
||||||
|
"step1Desc": " • Enter name, age, city\\n • Add description\\n • Upload photo",
|
||||||
|
"step2": "2️⃣ Browse profiles",
|
||||||
|
"step2Desc": " • Swipe through other users' profiles\\n • Like (❤️) or dislike (👎)",
|
||||||
|
"step3": "3️⃣ Get match",
|
||||||
|
"step3Desc": " • When two people like each other\\n • Chat becomes available",
|
||||||
|
"step4": "4️⃣ Communication",
|
||||||
|
"step4Desc": " • Find common interests\\n • Arrange meetings",
|
||||||
|
"tipsTitle": "💡 Tips:",
|
||||||
|
"tips": "• Use quality photos\\n• Write interesting description\\n• Be polite in communication",
|
||||||
|
"createProfile": "🚀 Create profile"
|
||||||
|
},
|
||||||
|
"noProfile": {
|
||||||
|
"message": "❌ You don't have a profile yet.\\nCreate one to start using the bot!",
|
||||||
|
"createButton": "🚀 Create profile"
|
||||||
|
},
|
||||||
|
"profileCreated": {
|
||||||
|
"success": "🎉 Profile created successfully!\\n\\nWelcome, {{name}}! 💖\\n\\nNow you can start searching for your soulmate!",
|
||||||
|
"myProfile": "👤 My profile",
|
||||||
|
"startSearch": "🔍 Start search"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Profile not found",
|
||||||
|
"profileIncomplete": "Fill out the profile completely",
|
||||||
|
"ageInvalid": "Enter correct age (18-100)",
|
||||||
|
"photoRequired": "Add at least one photo",
|
||||||
|
"networkError": "Network error. Try later.",
|
||||||
|
"serverError": "Server error. Try later."
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/locales/es.json
Normal file
101
src/locales/es.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "¡Bienvenido al Bot de Tinder en Telegram! 💕",
|
||||||
|
"description": "¡Encuentra a tu alma gemela aquí mismo!",
|
||||||
|
"getStarted": "Comenzar"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "Crear Perfil",
|
||||||
|
"edit": "Editar Perfil",
|
||||||
|
"view": "Ver Perfil",
|
||||||
|
"name": "Nombre",
|
||||||
|
"age": "Edad",
|
||||||
|
"city": "Ciudad",
|
||||||
|
"bio": "Acerca de",
|
||||||
|
"photos": "Fotos",
|
||||||
|
"gender": "Género",
|
||||||
|
"lookingFor": "Buscando",
|
||||||
|
"datingGoal": "Objetivo de Cita",
|
||||||
|
"hobbies": "Aficiones",
|
||||||
|
"lifestyle": "Estilo de Vida",
|
||||||
|
"male": "Masculino",
|
||||||
|
"female": "Femenino",
|
||||||
|
"both": "Ambos",
|
||||||
|
"relationship": "Relación",
|
||||||
|
"friendship": "Amistad",
|
||||||
|
"dating": "Citas",
|
||||||
|
"hookup": "Aventura",
|
||||||
|
"marriage": "Matrimonio",
|
||||||
|
"networking": "Networking",
|
||||||
|
"travel": "Viajes",
|
||||||
|
"business": "Negocios",
|
||||||
|
"other": "Otro"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "Explorar Perfiles",
|
||||||
|
"noProfiles": "¡No hay más perfiles! Inténtalo más tarde.",
|
||||||
|
"like": "❤️ Me Gusta",
|
||||||
|
"dislike": "👎 Pasar",
|
||||||
|
"superLike": "⭐ Super Like",
|
||||||
|
"match": "¡Es un match! 🎉"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "Búsqueda VIP",
|
||||||
|
"premiumRequired": "Esta función está disponible solo para usuarios premium",
|
||||||
|
"filters": "Filtros",
|
||||||
|
"ageRange": "Rango de Edad",
|
||||||
|
"cityFilter": "Ciudad",
|
||||||
|
"datingGoalFilter": "Objetivo de Cita",
|
||||||
|
"hobbiesFilter": "Aficiones",
|
||||||
|
"lifestyleFilter": "Estilo de Vida",
|
||||||
|
"applyFilters": "Aplicar Filtros",
|
||||||
|
"clearFilters": "Limpiar Filtros",
|
||||||
|
"noResults": "No se encontraron perfiles con tus filtros",
|
||||||
|
"translateProfile": "🌐 Traducir Perfil"
|
||||||
|
},
|
||||||
|
"premium": {
|
||||||
|
"title": "Suscripción Premium",
|
||||||
|
"features": "Características premium:",
|
||||||
|
"vipSearch": "• Búsqueda VIP con filtros",
|
||||||
|
"profileTranslation": "• Traducción de perfiles a tu idioma",
|
||||||
|
"unlimitedLikes": "• Me gusta ilimitados",
|
||||||
|
"superLikes": "• Super likes adicionales",
|
||||||
|
"price": "Precio: $4.99/mes",
|
||||||
|
"activate": "Activar Premium"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"translating": "Traduciendo perfil...",
|
||||||
|
"translated": "Perfil traducido:",
|
||||||
|
"error": "Error de traducción. Por favor, inténtalo más tarde.",
|
||||||
|
"premiumOnly": "La traducción está disponible solo para usuarios premium"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "Menú Principal",
|
||||||
|
"profile": "Mi Perfil",
|
||||||
|
"search": "Explorar",
|
||||||
|
"vip": "Búsqueda VIP",
|
||||||
|
"matches": "Matches",
|
||||||
|
"premium": "Premium",
|
||||||
|
"settings": "Configuración",
|
||||||
|
"help": "Ayuda"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« Atrás",
|
||||||
|
"next": "Siguiente »",
|
||||||
|
"save": "Guardar",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"edit": "Editar",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"yes": "Sí",
|
||||||
|
"no": "No"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Perfil no encontrado",
|
||||||
|
"profileIncomplete": "Por favor completa tu perfil",
|
||||||
|
"ageInvalid": "Por favor ingresa una edad válida (18-100)",
|
||||||
|
"photoRequired": "Por favor agrega al menos una foto",
|
||||||
|
"networkError": "Error de red. Por favor inténtalo más tarde.",
|
||||||
|
"serverError": "Error del servidor. Por favor inténtalo más tarde."
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/locales/es_fixed.json
Normal file
94
src/locales/es_fixed.json
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"commands": {
|
||||||
|
"start": "🏠 Menú principal",
|
||||||
|
"help": "ℹ️ Ayuda",
|
||||||
|
"profile": "👤 Mi perfil",
|
||||||
|
"search": "🔍 Buscar perfiles",
|
||||||
|
"matches": "💕 Matches",
|
||||||
|
"premium": "⭐ Premium",
|
||||||
|
"settings": "⚙️ Configuración"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"main": "🏠 Menú principal",
|
||||||
|
"back": "👈 Atrás",
|
||||||
|
"profile": "👤 Perfil",
|
||||||
|
"search": "🔍 Buscar",
|
||||||
|
"matches": "💕 Matches",
|
||||||
|
"premium": "⭐ Premium",
|
||||||
|
"settings": "⚙️ Configuración"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"newUser": "¡Bienvenido a Telegram Tinder Bot! 💕\\n\\nAquí puedes encontrar personas interesantes para comunicarte y conocer.\\n\\n¡Para comenzar, crea tu perfil!",
|
||||||
|
"existingUser": "¡Bienvenido de vuelta! 👋\\n\\nElige una acción:",
|
||||||
|
"createProfile": "🚀 Crear perfil"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"title": "📋 Cómo usar el bot:",
|
||||||
|
"step1": "1️⃣ Crear perfil",
|
||||||
|
"step1Desc": " • Indica nombre, edad, ciudad\\n • Agrega descripción\\n • Sube una foto",
|
||||||
|
"step2": "2️⃣ Navegar perfiles",
|
||||||
|
"step2Desc": " • Desliza por los perfiles de otros usuarios\\n • Dale me gusta (❤️) o no me gusta (👎)",
|
||||||
|
"step3": "3️⃣ Obtener match",
|
||||||
|
"step3Desc": " • Cuando dos personas se gustan mutuamente\\n • Se habilita el chat",
|
||||||
|
"step4": "4️⃣ Comunicación",
|
||||||
|
"step4Desc": " • Encuentra intereses comunes\\n • Organiza encuentros",
|
||||||
|
"tipsTitle": "💡 Consejos:",
|
||||||
|
"tips": "• Usa fotos de calidad\\n• Escribe una descripción interesante\\n• Sé educado en la comunicación",
|
||||||
|
"createProfile": "🚀 Crear perfil"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "⚙️ Configuración",
|
||||||
|
"language": "🌐 Idioma de la interfaz",
|
||||||
|
"ageRange": "📅 Rango de edad",
|
||||||
|
"showAge": "🎂 Mostrar edad",
|
||||||
|
"showCity": "📍 Mostrar ciudad",
|
||||||
|
"notifications": "🔔 Notificaciones",
|
||||||
|
"privacy": "🔒 Privacidad",
|
||||||
|
"back": "👈 Atrás"
|
||||||
|
},
|
||||||
|
"languages": {
|
||||||
|
"ru": "🇷🇺 Русский",
|
||||||
|
"en": "🇺🇸 English",
|
||||||
|
"es": "🇪🇸 Español",
|
||||||
|
"fr": "🇫🇷 Français",
|
||||||
|
"de": "🇩🇪 Deutsch",
|
||||||
|
"it": "🇮🇹 Italiano",
|
||||||
|
"pt": "🇵🇹 Português",
|
||||||
|
"zh": "🇨🇳 中文",
|
||||||
|
"ja": "🇯🇵 日本語",
|
||||||
|
"ko": "🇰🇷 한국어",
|
||||||
|
"uz": "🇺🇿 O'zbekcha",
|
||||||
|
"kk": "🇰🇿 Қазақша"
|
||||||
|
},
|
||||||
|
"howItWorks": {
|
||||||
|
"title": "🤔 ¿Cómo funciona?",
|
||||||
|
"step1": "1️⃣ Crear perfil",
|
||||||
|
"step1Desc": " • Indica nombre, edad, ciudad\\n • Agrega descripción\\n • Sube una foto",
|
||||||
|
"step2": "2️⃣ Navegar perfiles",
|
||||||
|
"step2Desc": " • Desliza por los perfiles de otros usuarios\\n • Dale me gusta (❤️) o no me gusta (👎)",
|
||||||
|
"step3": "3️⃣ Obtener match",
|
||||||
|
"step3Desc": " • Cuando dos personas se gustan mutuamente\\n • Se habilita el chat",
|
||||||
|
"step4": "4️⃣ Comunicación",
|
||||||
|
"step4Desc": " • Encuentra intereses comunes\\n • Organiza encuentros",
|
||||||
|
"tipsTitle": "💡 Consejos:",
|
||||||
|
"tips": "• Usa fotos de calidad\\n• Escribe una descripción interesante\\n• Sé educado en la comunicación",
|
||||||
|
"createProfile": "🚀 Crear perfil"
|
||||||
|
},
|
||||||
|
"noProfile": {
|
||||||
|
"message": "❌ Aún no tienes un perfil.\\n¡Crea uno para empezar a usar el bot!",
|
||||||
|
"createButton": "🚀 Crear perfil"
|
||||||
|
},
|
||||||
|
"profileCreated": {
|
||||||
|
"success": "🎉 ¡Perfil creado exitosamente!\\n\\n¡Bienvenido, {{name}}! 💖\\n\\n¡Ahora puedes empezar a buscar tu media naranja!",
|
||||||
|
"myProfile": "👤 Mi perfil",
|
||||||
|
"startSearch": "🔍 Comenzar búsqueda"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Perfil no encontrado",
|
||||||
|
"profileIncomplete": "Completa el perfil por completo",
|
||||||
|
"ageInvalid": "Ingresa edad correcta (18-100)",
|
||||||
|
"photoRequired": "Agrega al menos una foto",
|
||||||
|
"networkError": "Error de red. Intenta más tarde.",
|
||||||
|
"serverError": "Error del servidor. Intenta más tarde."
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/locales/fr.json
Normal file
101
src/locales/fr.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "Bienvenue sur le Bot Tinder Telegram ! 💕",
|
||||||
|
"description": "Trouvez votre âme sœur ici même !",
|
||||||
|
"getStarted": "Commencer"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "Créer un Profil",
|
||||||
|
"edit": "Modifier le Profil",
|
||||||
|
"view": "Voir le Profil",
|
||||||
|
"name": "Nom",
|
||||||
|
"age": "Âge",
|
||||||
|
"city": "Ville",
|
||||||
|
"bio": "À propos",
|
||||||
|
"photos": "Photos",
|
||||||
|
"gender": "Genre",
|
||||||
|
"lookingFor": "Recherche",
|
||||||
|
"datingGoal": "Objectif de Rencontre",
|
||||||
|
"hobbies": "Loisirs",
|
||||||
|
"lifestyle": "Style de Vie",
|
||||||
|
"male": "Masculin",
|
||||||
|
"female": "Féminin",
|
||||||
|
"both": "Les Deux",
|
||||||
|
"relationship": "Relation",
|
||||||
|
"friendship": "Amitié",
|
||||||
|
"dating": "Rendez-vous",
|
||||||
|
"hookup": "Aventure",
|
||||||
|
"marriage": "Mariage",
|
||||||
|
"networking": "Réseautage",
|
||||||
|
"travel": "Voyage",
|
||||||
|
"business": "Affaires",
|
||||||
|
"other": "Autre"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "Parcourir les Profils",
|
||||||
|
"noProfiles": "Plus de profils ! Réessayez plus tard.",
|
||||||
|
"like": "❤️ J'aime",
|
||||||
|
"dislike": "👎 Passer",
|
||||||
|
"superLike": "⭐ Super Like",
|
||||||
|
"match": "C'est un match ! 🎉"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "Recherche VIP",
|
||||||
|
"premiumRequired": "Cette fonction est disponible uniquement pour les utilisateurs premium",
|
||||||
|
"filters": "Filtres",
|
||||||
|
"ageRange": "Tranche d'Âge",
|
||||||
|
"cityFilter": "Ville",
|
||||||
|
"datingGoalFilter": "Objectif de Rencontre",
|
||||||
|
"hobbiesFilter": "Loisirs",
|
||||||
|
"lifestyleFilter": "Style de Vie",
|
||||||
|
"applyFilters": "Appliquer les Filtres",
|
||||||
|
"clearFilters": "Effacer les Filtres",
|
||||||
|
"noResults": "Aucun profil trouvé avec vos filtres",
|
||||||
|
"translateProfile": "🌐 Traduire le Profil"
|
||||||
|
},
|
||||||
|
"premium": {
|
||||||
|
"title": "Abonnement Premium",
|
||||||
|
"features": "Fonctionnalités premium :",
|
||||||
|
"vipSearch": "• Recherche VIP avec filtres",
|
||||||
|
"profileTranslation": "• Traduction de profils dans votre langue",
|
||||||
|
"unlimitedLikes": "• J'aime illimités",
|
||||||
|
"superLikes": "• Super likes supplémentaires",
|
||||||
|
"price": "Prix : 4,99€/mois",
|
||||||
|
"activate": "Activer Premium"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"translating": "Traduction du profil...",
|
||||||
|
"translated": "Profil traduit :",
|
||||||
|
"error": "Erreur de traduction. Veuillez réessayer plus tard.",
|
||||||
|
"premiumOnly": "La traduction est disponible uniquement pour les utilisateurs premium"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "Menu Principal",
|
||||||
|
"profile": "Mon Profil",
|
||||||
|
"search": "Parcourir",
|
||||||
|
"vip": "Recherche VIP",
|
||||||
|
"matches": "Matches",
|
||||||
|
"premium": "Premium",
|
||||||
|
"settings": "Paramètres",
|
||||||
|
"help": "Aide"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« Retour",
|
||||||
|
"next": "Suivant »",
|
||||||
|
"save": "Sauvegarder",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"confirm": "Confirmer",
|
||||||
|
"edit": "Modifier",
|
||||||
|
"delete": "Supprimer",
|
||||||
|
"yes": "Oui",
|
||||||
|
"no": "Non"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Profil non trouvé",
|
||||||
|
"profileIncomplete": "Veuillez compléter votre profil",
|
||||||
|
"ageInvalid": "Veuillez entrer un âge valide (18-100)",
|
||||||
|
"photoRequired": "Veuillez ajouter au moins une photo",
|
||||||
|
"networkError": "Erreur réseau. Veuillez réessayer plus tard.",
|
||||||
|
"serverError": "Erreur serveur. Veuillez réessayer plus tard."
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/locales/it.json
Normal file
101
src/locales/it.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "Benvenuto su Telegram Tinder Bot! 💕",
|
||||||
|
"description": "Trova la tua anima gemella proprio qui!",
|
||||||
|
"getStarted": "Inizia"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "Crea Profilo",
|
||||||
|
"edit": "Modifica Profilo",
|
||||||
|
"view": "Visualizza Profilo",
|
||||||
|
"name": "Nome",
|
||||||
|
"age": "Età",
|
||||||
|
"city": "Città",
|
||||||
|
"bio": "Info",
|
||||||
|
"photos": "Foto",
|
||||||
|
"gender": "Genere",
|
||||||
|
"lookingFor": "Cerco",
|
||||||
|
"datingGoal": "Obiettivo Appuntamenti",
|
||||||
|
"hobbies": "Hobby",
|
||||||
|
"lifestyle": "Stile di Vita",
|
||||||
|
"male": "Maschio",
|
||||||
|
"female": "Femmina",
|
||||||
|
"both": "Entrambi",
|
||||||
|
"relationship": "Relazione",
|
||||||
|
"friendship": "Amicizia",
|
||||||
|
"dating": "Appuntamenti",
|
||||||
|
"hookup": "Avventura",
|
||||||
|
"marriage": "Matrimonio",
|
||||||
|
"networking": "Networking",
|
||||||
|
"travel": "Viaggi",
|
||||||
|
"business": "Affari",
|
||||||
|
"other": "Altro"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "Sfoglia Profili",
|
||||||
|
"noProfiles": "Nessun altro profilo! Riprova più tardi.",
|
||||||
|
"like": "❤️ Mi Piace",
|
||||||
|
"dislike": "👎 Salta",
|
||||||
|
"superLike": "⭐ Super Like",
|
||||||
|
"match": "È un match! 🎉"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "Ricerca VIP",
|
||||||
|
"premiumRequired": "Questa funzione è disponibile solo per utenti premium",
|
||||||
|
"filters": "Filtri",
|
||||||
|
"ageRange": "Fascia di Età",
|
||||||
|
"cityFilter": "Città",
|
||||||
|
"datingGoalFilter": "Obiettivo Appuntamenti",
|
||||||
|
"hobbiesFilter": "Hobby",
|
||||||
|
"lifestyleFilter": "Stile di Vita",
|
||||||
|
"applyFilters": "Applica Filtri",
|
||||||
|
"clearFilters": "Cancella Filtri",
|
||||||
|
"noResults": "Nessun profilo trovato con i tuoi filtri",
|
||||||
|
"translateProfile": "🌐 Traduci Profilo"
|
||||||
|
},
|
||||||
|
"premium": {
|
||||||
|
"title": "Abbonamento Premium",
|
||||||
|
"features": "Funzionalità premium:",
|
||||||
|
"vipSearch": "• Ricerca VIP con filtri",
|
||||||
|
"profileTranslation": "• Traduzione profili nella tua lingua",
|
||||||
|
"unlimitedLikes": "• Mi piace illimitati",
|
||||||
|
"superLikes": "• Super like extra",
|
||||||
|
"price": "Prezzo: €4,99/mese",
|
||||||
|
"activate": "Attiva Premium"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"translating": "Traduzione profilo...",
|
||||||
|
"translated": "Profilo tradotto:",
|
||||||
|
"error": "Errore di traduzione. Riprova più tardi.",
|
||||||
|
"premiumOnly": "La traduzione è disponibile solo per utenti premium"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "Menu Principale",
|
||||||
|
"profile": "Il Mio Profilo",
|
||||||
|
"search": "Sfoglia",
|
||||||
|
"vip": "Ricerca VIP",
|
||||||
|
"matches": "Match",
|
||||||
|
"premium": "Premium",
|
||||||
|
"settings": "Impostazioni",
|
||||||
|
"help": "Aiuto"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« Indietro",
|
||||||
|
"next": "Avanti »",
|
||||||
|
"save": "Salva",
|
||||||
|
"cancel": "Annulla",
|
||||||
|
"confirm": "Conferma",
|
||||||
|
"edit": "Modifica",
|
||||||
|
"delete": "Elimina",
|
||||||
|
"yes": "Sì",
|
||||||
|
"no": "No"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Profilo non trovato",
|
||||||
|
"profileIncomplete": "Per favore completa il tuo profilo",
|
||||||
|
"ageInvalid": "Per favore inserisci un'età valida (18-100)",
|
||||||
|
"photoRequired": "Per favore aggiungi almeno una foto",
|
||||||
|
"networkError": "Errore di rete. Riprova più tardi.",
|
||||||
|
"serverError": "Errore del server. Riprova più tardi."
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/locales/ja.json
Normal file
101
src/locales/ja.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "Telegram Tinder Botへようこそ!💕",
|
||||||
|
"description": "ここであなたの運命の人を見つけましょう!",
|
||||||
|
"getStarted": "始める"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "プロフィール作成",
|
||||||
|
"edit": "プロフィール編集",
|
||||||
|
"view": "プロフィール表示",
|
||||||
|
"name": "名前",
|
||||||
|
"age": "年齢",
|
||||||
|
"city": "都市",
|
||||||
|
"bio": "自己紹介",
|
||||||
|
"photos": "写真",
|
||||||
|
"gender": "性別",
|
||||||
|
"lookingFor": "探している相手",
|
||||||
|
"datingGoal": "出会いの目的",
|
||||||
|
"hobbies": "趣味",
|
||||||
|
"lifestyle": "ライフスタイル",
|
||||||
|
"male": "男性",
|
||||||
|
"female": "女性",
|
||||||
|
"both": "どちらでも",
|
||||||
|
"relationship": "恋愛関係",
|
||||||
|
"friendship": "友達",
|
||||||
|
"dating": "デート",
|
||||||
|
"hookup": "カジュアル",
|
||||||
|
"marriage": "結婚",
|
||||||
|
"networking": "ネットワーキング",
|
||||||
|
"travel": "旅行",
|
||||||
|
"business": "ビジネス",
|
||||||
|
"other": "その他"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "プロフィール閲覧",
|
||||||
|
"noProfiles": "これ以上プロフィールがありません!後でもう一度お試しください。",
|
||||||
|
"like": "❤️ いいね",
|
||||||
|
"dislike": "👎 スキップ",
|
||||||
|
"superLike": "⭐ スーパーライク",
|
||||||
|
"match": "マッチしました!🎉"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "VIP検索",
|
||||||
|
"premiumRequired": "この機能はプレミアムユーザーのみご利用いただけます",
|
||||||
|
"filters": "フィルター",
|
||||||
|
"ageRange": "年齢範囲",
|
||||||
|
"cityFilter": "都市",
|
||||||
|
"datingGoalFilter": "出会いの目的",
|
||||||
|
"hobbiesFilter": "趣味",
|
||||||
|
"lifestyleFilter": "ライフスタイル",
|
||||||
|
"applyFilters": "フィルター適用",
|
||||||
|
"clearFilters": "フィルタークリア",
|
||||||
|
"noResults": "フィルター条件に一致するプロフィールが見つかりません",
|
||||||
|
"translateProfile": "🌐 プロフィール翻訳"
|
||||||
|
},
|
||||||
|
"premium": {
|
||||||
|
"title": "プレミアム購読",
|
||||||
|
"features": "プレミアム機能:",
|
||||||
|
"vipSearch": "• フィルター付きVIP検索",
|
||||||
|
"profileTranslation": "• プロフィールをあなたの言語に翻訳",
|
||||||
|
"unlimitedLikes": "• 無制限いいね",
|
||||||
|
"superLikes": "• 追加スーパーライク",
|
||||||
|
"price": "価格:¥650/月",
|
||||||
|
"activate": "プレミアム有効化"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"translating": "プロフィールを翻訳中...",
|
||||||
|
"translated": "翻訳されたプロフィール:",
|
||||||
|
"error": "翻訳エラー。後でもう一度お試しください。",
|
||||||
|
"premiumOnly": "翻訳機能はプレミアムユーザーのみご利用いただけます"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "メインメニュー",
|
||||||
|
"profile": "マイプロフィール",
|
||||||
|
"search": "閲覧",
|
||||||
|
"vip": "VIP検索",
|
||||||
|
"matches": "マッチ",
|
||||||
|
"premium": "プレミアム",
|
||||||
|
"settings": "設定",
|
||||||
|
"help": "ヘルプ"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« 戻る",
|
||||||
|
"next": "次へ »",
|
||||||
|
"save": "保存",
|
||||||
|
"cancel": "キャンセル",
|
||||||
|
"confirm": "確認",
|
||||||
|
"edit": "編集",
|
||||||
|
"delete": "削除",
|
||||||
|
"yes": "はい",
|
||||||
|
"no": "いいえ"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "プロフィールが見つかりません",
|
||||||
|
"profileIncomplete": "プロフィールを完成させてください",
|
||||||
|
"ageInvalid": "有効な年齢を入力してください(18-100)",
|
||||||
|
"photoRequired": "最低1枚の写真を追加してください",
|
||||||
|
"networkError": "ネットワークエラー。後でもう一度お試しください。",
|
||||||
|
"serverError": "サーバーエラー。後でもう一度お試しください。"
|
||||||
|
}
|
||||||
|
}
|
||||||
152
src/locales/kk.json
Normal file
152
src/locales/kk.json
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "🎉 Telegram Tinder Botқа қош келдіңіз!\n\n💕 Мұнда сіз өзіңіздің жарыңызды таба аласыз!\n\nБастау үшін профиліңізді жасаңыз:",
|
||||||
|
"description": "Өзіңіздің жарыңызды осы жерден табыңыз!",
|
||||||
|
"getStarted": "Танысуды бастау",
|
||||||
|
"haveProfile": "🎉 Қош келдіңіз, {{name}}!\n\n💖 Telegram Tinder Bot жұмысқа дайын!\n\nНе істегіңіз келеді?"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "Профиль жасау",
|
||||||
|
"edit": "Профильді өңдеу",
|
||||||
|
"view": "Профильді көру",
|
||||||
|
"name": "Аты",
|
||||||
|
"age": "Жасы",
|
||||||
|
"city": "Қала",
|
||||||
|
"bio": "Өзім туралы",
|
||||||
|
"photos": "Суреттер",
|
||||||
|
"gender": "Жынысы",
|
||||||
|
"lookingFor": "Іздеймін",
|
||||||
|
"datingGoal": "Танысу мақсаты",
|
||||||
|
"hobbies": "Хоббилер",
|
||||||
|
"lifestyle": "Өмір салты",
|
||||||
|
"male": "Ер",
|
||||||
|
"female": "Әйел",
|
||||||
|
"both": "Маңызды емес",
|
||||||
|
"relationship": "Серьезды қатынас",
|
||||||
|
"friendship": "Достық",
|
||||||
|
"dating": "Кездесулер",
|
||||||
|
"hookup": "Қысқа қатынас",
|
||||||
|
"marriage": "Неке",
|
||||||
|
"networking": "Қарым-қатынас",
|
||||||
|
"travel": "Саяхат",
|
||||||
|
"business": "Бизнес",
|
||||||
|
"other": "Басқа"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "Профильдерді іздеу",
|
||||||
|
"noProfiles": "Профильдер таусылды! Кейінірек көріңіз.",
|
||||||
|
"like": "👍 Ұнайды",
|
||||||
|
"dislike": "👎 Ұнамайды",
|
||||||
|
"superlike": "💖 Супер ұнайды",
|
||||||
|
"match": "Бұл өзара ұнау! 🎉",
|
||||||
|
"tryAgain": "🔄 Қайта көру",
|
||||||
|
"myMatches": "💕 Менің матчтарым",
|
||||||
|
"allViewed": "🎉 Сіз барлық қолжетімді кандидаттарды қарап шықтыңыз!\n\n⏰ Кейінірек көріңіз - жаңа профильдер пайда болуы мүмкін!",
|
||||||
|
"viewProfile": "👤 Профиль",
|
||||||
|
"morePhotos": "📸 Тағы суреттер",
|
||||||
|
"next": "⏭ Келесі",
|
||||||
|
"sendMessage": "💬 Хабар жазу",
|
||||||
|
"continueBrowsing": "🔍 Іздеуді жалғастыру",
|
||||||
|
"matchFound": "🎉 БҰЛ МАТЧ! 💕\n\n{{name}} пен өзара ұнадыңыз!\n\nЕнді сөйлесуді бастай аласыз!",
|
||||||
|
"noMoreProfiles": "😔 Қазір жаңа профильдер жоқ.\n\n⏰ Кейінірек қайта келуіңізге болады!"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "⭐ VIP Іздеу",
|
||||||
|
"description": "Премиум мүмкіндіктермен іздеңіз!",
|
||||||
|
"features": "• Шексіз лайктар\n• Супер лайктар\n• Кімдер ұнатқанын көру\n• Жарнамасыз тәжірибе",
|
||||||
|
"getVip": "VIP алу",
|
||||||
|
"alreadyVip": "Сіз қазірдің өзінде VIP пайдаланушысыз!"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"inProgress": "🔄 Аударылуда...",
|
||||||
|
"completed": "✅ Аударма дайын!",
|
||||||
|
"failed": "❌ Аударма қатесі",
|
||||||
|
"error": "Аударма қатесі. Кейінірек көріңіз.",
|
||||||
|
"premiumOnly": "Аударма тек премиум пайдаланушылар үшін"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "Басты мәзір",
|
||||||
|
"profile": "Менің профилім",
|
||||||
|
"search": "Іздеу",
|
||||||
|
"vip": "VIP іздеу",
|
||||||
|
"matches": "Өзара ұнатулар",
|
||||||
|
"premium": "Премиум",
|
||||||
|
"settings": "Баптаулар",
|
||||||
|
"help": "Көмек"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« Артқа",
|
||||||
|
"next": "Келесі »",
|
||||||
|
"save": "Сақтау",
|
||||||
|
"cancel": "Бас тарту",
|
||||||
|
"confirm": "Растау",
|
||||||
|
"edit": "Өңдеу",
|
||||||
|
"delete": "Жою",
|
||||||
|
"yes": "Иә",
|
||||||
|
"no": "Жоқ"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"title": "🤖 Telegram Tinder Bot - Көмек",
|
||||||
|
"commands": "📋 Қолжетімді командалар:",
|
||||||
|
"commandStart": "/start - Басты мәзір",
|
||||||
|
"commandProfile": "/profile - Профиль басқаруы",
|
||||||
|
"commandBrowse": "/browse - Профильдерді көру",
|
||||||
|
"commandMatches": "/matches - Сіздің матчтарыңыз",
|
||||||
|
"commandSettings": "/settings - Баптаулар",
|
||||||
|
"commandHelp": "/help - Осы көмек",
|
||||||
|
"howToUse": "📱 Қалай пайдалану:",
|
||||||
|
"step1": "1. Сурет пен сипаттамамен профиль жасаңыз",
|
||||||
|
"step2": "2. Басқа пайдаланушылардың профильдерін қараңыз",
|
||||||
|
"step3": "3. Ұнағандарға лайк басыңыз",
|
||||||
|
"step4": "4. Өзара ұнатқандармен сөйлесіңіз!",
|
||||||
|
"goodLuck": "❤️ Махаббат табуда сәттілік тілейміз!"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "⚙️ Профиль баптаулары\n\nӨзгерткіңіз келетін нәрсені таңдаңыз:",
|
||||||
|
"searchSettings": "🔍 Іздеу баптаулары",
|
||||||
|
"notifications": "🔔 Хабарландырулар",
|
||||||
|
"language": "🌐 Интерфейс тілі",
|
||||||
|
"stats": "📊 Статистика",
|
||||||
|
"hideProfile": "🚫 Профильді жасыру",
|
||||||
|
"deleteProfile": "🗑 Профильді жою",
|
||||||
|
"searchComingSoon": "🔍 Іздеу баптаулары келесі жаңартуда болады!",
|
||||||
|
"notificationsComingSoon": "🔔 Хабарландыру баптаулары келесі жаңартуда болады!"
|
||||||
|
},
|
||||||
|
"howItWorks": {
|
||||||
|
"title": "🎯 Telegram Tinder Bot қалай жұмыс істейді?",
|
||||||
|
"step1Title": "1️⃣ Профиль жасаңыз",
|
||||||
|
"step1Desc": " • Сурет пен сипаттама қосыңыз\n • Өзіңіздің қалауларыңызды белгілеңіз",
|
||||||
|
"step2Title": "2️⃣ Профильдерді қараңыз",
|
||||||
|
"step2Desc": " • Ұнағандарға лайк басыңыз\n • Ерекше жағдайлар үшін супер лайк пайдаланыңыз",
|
||||||
|
"step3Title": "3️⃣ Матчтар алыңыз",
|
||||||
|
"step3Desc": " • Лайкіңіз өзара болса - бұл матч!\n • Сөйлесуді бастаңыз",
|
||||||
|
"step4Title": "4️⃣ Сөйлесіңіз және танысыңыз",
|
||||||
|
"step4Desc": " • Ортақ қызығушылықтарды табыңыз\n • Кездесуді жоспарлаңыз",
|
||||||
|
"tipsTitle": "💡 Кеңестер:",
|
||||||
|
"tips": "• Сапалы суреттер пайдаланыңыз\n• Қызықты сипаттама жазыңыз\n• Сөйлесуде сыпайы болыңыз",
|
||||||
|
"createProfile": "🚀 Профиль жасау"
|
||||||
|
},
|
||||||
|
"noProfile": {
|
||||||
|
"message": "❌ Сізде әлі профиль жоқ.\\nБотты пайдалану үшін профиль жасаңыз!",
|
||||||
|
"createButton": "🚀 Профиль жасау"
|
||||||
|
},
|
||||||
|
"noMatches": {
|
||||||
|
"message": "💔 Сізде әлі матчтар жоқ.\\n\\n🔍 Көбірек профильдерді қарап шығыңыз!\\nІздеу үшін /browse пайдаланыңыз."
|
||||||
|
},
|
||||||
|
"browsing": {
|
||||||
|
"needProfile": "❌ Алдымен профиль жасаңыз!\\n/start командасын пайдаланыңыз"
|
||||||
|
},
|
||||||
|
"profileCreated": {
|
||||||
|
"success": "🎉 Профиль сәтті жасалды!\n\nҚош келдіңіз, {{name}}! 💖\n\nЕнді сіз өзіңіздің жарыңызды іздеуді бастай аласыз!",
|
||||||
|
"myProfile": "👤 Менің профилім",
|
||||||
|
"startSearch": "🔍 Іздеуді бастау"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Профиль табылмады",
|
||||||
|
"profileIncomplete": "Профильді толық толтырыңыз",
|
||||||
|
"ageInvalid": "Дұрыс жасты енгізіңіз (18-100)",
|
||||||
|
"photoRequired": "Кемінде бір сурет қосыңыз",
|
||||||
|
"networkError": "Желі қатесі. Кейінірек көріңіз.",
|
||||||
|
"serverError": "Сервер қатесі. Кейінірек көріңіз."
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/locales/ko.json
Normal file
101
src/locales/ko.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "텔레그램 틴더 봇에 오신 것을 환영합니다! 💕",
|
||||||
|
"description": "바로 여기서 당신의 소울메이트를 찾아보세요!",
|
||||||
|
"getStarted": "시작하기"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "프로필 생성",
|
||||||
|
"edit": "프로필 수정",
|
||||||
|
"view": "프로필 보기",
|
||||||
|
"name": "이름",
|
||||||
|
"age": "나이",
|
||||||
|
"city": "도시",
|
||||||
|
"bio": "자기소개",
|
||||||
|
"photos": "사진",
|
||||||
|
"gender": "성별",
|
||||||
|
"lookingFor": "찾는 상대",
|
||||||
|
"datingGoal": "만남 목적",
|
||||||
|
"hobbies": "취미",
|
||||||
|
"lifestyle": "라이프스타일",
|
||||||
|
"male": "남성",
|
||||||
|
"female": "여성",
|
||||||
|
"both": "상관없음",
|
||||||
|
"relationship": "진지한 관계",
|
||||||
|
"friendship": "친구",
|
||||||
|
"dating": "데이트",
|
||||||
|
"hookup": "가벼운 만남",
|
||||||
|
"marriage": "결혼",
|
||||||
|
"networking": "네트워킹",
|
||||||
|
"travel": "여행",
|
||||||
|
"business": "비즈니스",
|
||||||
|
"other": "기타"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "프로필 둘러보기",
|
||||||
|
"noProfiles": "더 이상 프로필이 없습니다! 나중에 다시 시도해보세요.",
|
||||||
|
"like": "❤️ 좋아요",
|
||||||
|
"dislike": "👎 패스",
|
||||||
|
"superLike": "⭐ 슈퍼 라이크",
|
||||||
|
"match": "매치 성공! 🎉"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "VIP 검색",
|
||||||
|
"premiumRequired": "이 기능은 프리미엄 사용자만 이용할 수 있습니다",
|
||||||
|
"filters": "필터",
|
||||||
|
"ageRange": "연령대",
|
||||||
|
"cityFilter": "도시",
|
||||||
|
"datingGoalFilter": "만남 목적",
|
||||||
|
"hobbiesFilter": "취미",
|
||||||
|
"lifestyleFilter": "라이프스타일",
|
||||||
|
"applyFilters": "필터 적용",
|
||||||
|
"clearFilters": "필터 초기화",
|
||||||
|
"noResults": "필터 조건에 맞는 프로필을 찾을 수 없습니다",
|
||||||
|
"translateProfile": "🌐 프로필 번역"
|
||||||
|
},
|
||||||
|
"premium": {
|
||||||
|
"title": "프리미엄 구독",
|
||||||
|
"features": "프리미엄 기능:",
|
||||||
|
"vipSearch": "• 필터가 있는 VIP 검색",
|
||||||
|
"profileTranslation": "• 프로필을 내 언어로 번역",
|
||||||
|
"unlimitedLikes": "• 무제한 좋아요",
|
||||||
|
"superLikes": "• 추가 슈퍼 라이크",
|
||||||
|
"price": "가격: ₩5,900/월",
|
||||||
|
"activate": "프리미엄 활성화"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"translating": "프로필을 번역하는 중...",
|
||||||
|
"translated": "번역된 프로필:",
|
||||||
|
"error": "번역 오류. 나중에 다시 시도해주세요.",
|
||||||
|
"premiumOnly": "번역은 프리미엄 사용자만 이용할 수 있습니다"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "메인 메뉴",
|
||||||
|
"profile": "내 프로필",
|
||||||
|
"search": "둘러보기",
|
||||||
|
"vip": "VIP 검색",
|
||||||
|
"matches": "매치",
|
||||||
|
"premium": "프리미엄",
|
||||||
|
"settings": "설정",
|
||||||
|
"help": "도움말"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« 뒤로",
|
||||||
|
"next": "다음 »",
|
||||||
|
"save": "저장",
|
||||||
|
"cancel": "취소",
|
||||||
|
"confirm": "확인",
|
||||||
|
"edit": "수정",
|
||||||
|
"delete": "삭제",
|
||||||
|
"yes": "예",
|
||||||
|
"no": "아니오"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "프로필을 찾을 수 없습니다",
|
||||||
|
"profileIncomplete": "프로필을 완성해주세요",
|
||||||
|
"ageInvalid": "올바른 나이를 입력해주세요 (18-100)",
|
||||||
|
"photoRequired": "최소 한 장의 사진을 추가해주세요",
|
||||||
|
"networkError": "네트워크 오류. 나중에 다시 시도해주세요.",
|
||||||
|
"serverError": "서버 오류. 나중에 다시 시도해주세요."
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/locales/pt.json
Normal file
101
src/locales/pt.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "Bem-vindo ao Bot Tinder do Telegram! 💕",
|
||||||
|
"description": "Encontre sua alma gêmea bem aqui!",
|
||||||
|
"getStarted": "Começar"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "Criar Perfil",
|
||||||
|
"edit": "Editar Perfil",
|
||||||
|
"view": "Ver Perfil",
|
||||||
|
"name": "Nome",
|
||||||
|
"age": "Idade",
|
||||||
|
"city": "Cidade",
|
||||||
|
"bio": "Sobre",
|
||||||
|
"photos": "Fotos",
|
||||||
|
"gender": "Gênero",
|
||||||
|
"lookingFor": "Procurando",
|
||||||
|
"datingGoal": "Objetivo do Encontro",
|
||||||
|
"hobbies": "Hobbies",
|
||||||
|
"lifestyle": "Estilo de Vida",
|
||||||
|
"male": "Masculino",
|
||||||
|
"female": "Feminino",
|
||||||
|
"both": "Ambos",
|
||||||
|
"relationship": "Relacionamento",
|
||||||
|
"friendship": "Amizade",
|
||||||
|
"dating": "Encontros",
|
||||||
|
"hookup": "Aventura",
|
||||||
|
"marriage": "Casamento",
|
||||||
|
"networking": "Networking",
|
||||||
|
"travel": "Viagem",
|
||||||
|
"business": "Negócios",
|
||||||
|
"other": "Outro"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "Explorar Perfis",
|
||||||
|
"noProfiles": "Não há mais perfis! Tente novamente mais tarde.",
|
||||||
|
"like": "❤️ Curtir",
|
||||||
|
"dislike": "👎 Pular",
|
||||||
|
"superLike": "⭐ Super Like",
|
||||||
|
"match": "É um match! 🎉"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "Busca VIP",
|
||||||
|
"premiumRequired": "Este recurso está disponível apenas para usuários premium",
|
||||||
|
"filters": "Filtros",
|
||||||
|
"ageRange": "Faixa Etária",
|
||||||
|
"cityFilter": "Cidade",
|
||||||
|
"datingGoalFilter": "Objetivo do Encontro",
|
||||||
|
"hobbiesFilter": "Hobbies",
|
||||||
|
"lifestyleFilter": "Estilo de Vida",
|
||||||
|
"applyFilters": "Aplicar Filtros",
|
||||||
|
"clearFilters": "Limpar Filtros",
|
||||||
|
"noResults": "Nenhum perfil encontrado com seus filtros",
|
||||||
|
"translateProfile": "🌐 Traduzir Perfil"
|
||||||
|
},
|
||||||
|
"premium": {
|
||||||
|
"title": "Assinatura Premium",
|
||||||
|
"features": "Recursos premium:",
|
||||||
|
"vipSearch": "• Busca VIP com filtros",
|
||||||
|
"profileTranslation": "• Tradução de perfis para seu idioma",
|
||||||
|
"unlimitedLikes": "• Curtidas ilimitadas",
|
||||||
|
"superLikes": "• Super likes extras",
|
||||||
|
"price": "Preço: R$ 24,90/mês",
|
||||||
|
"activate": "Ativar Premium"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"translating": "Traduzindo perfil...",
|
||||||
|
"translated": "Perfil traduzido:",
|
||||||
|
"error": "Erro de tradução. Tente novamente mais tarde.",
|
||||||
|
"premiumOnly": "A tradução está disponível apenas para usuários premium"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "Menu Principal",
|
||||||
|
"profile": "Meu Perfil",
|
||||||
|
"search": "Explorar",
|
||||||
|
"vip": "Busca VIP",
|
||||||
|
"matches": "Matches",
|
||||||
|
"premium": "Premium",
|
||||||
|
"settings": "Configurações",
|
||||||
|
"help": "Ajuda"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« Voltar",
|
||||||
|
"next": "Próximo »",
|
||||||
|
"save": "Salvar",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"edit": "Editar",
|
||||||
|
"delete": "Excluir",
|
||||||
|
"yes": "Sim",
|
||||||
|
"no": "Não"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Perfil não encontrado",
|
||||||
|
"profileIncomplete": "Por favor, complete seu perfil",
|
||||||
|
"ageInvalid": "Por favor, insira uma idade válida (18-100)",
|
||||||
|
"photoRequired": "Por favor, adicione pelo menos uma foto",
|
||||||
|
"networkError": "Erro de rede. Tente novamente mais tarde.",
|
||||||
|
"serverError": "Erro do servidor. Tente novamente mais tarde."
|
||||||
|
}
|
||||||
|
}
|
||||||
129
src/locales/ru.json
Normal file
129
src/locales/ru.json
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "Добро пожаловать в Telegram Tinder Bot! 💕",
|
||||||
|
"description": "Найди свою вторую половинку прямо здесь!",
|
||||||
|
"getStarted": "Начать знакомство"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "Создать анкету",
|
||||||
|
"edit": "✏️ Редактировать",
|
||||||
|
"view": "Посмотреть анкету",
|
||||||
|
"name": "Имя",
|
||||||
|
"age": "Возраст",
|
||||||
|
"city": "Город",
|
||||||
|
"bio": "О себе",
|
||||||
|
"photos": "📸 Фото",
|
||||||
|
"gender": "Пол",
|
||||||
|
"lookingFor": "Ищу",
|
||||||
|
"datingGoal": "Цель знакомства",
|
||||||
|
"hobbies": "Хобби",
|
||||||
|
"lifestyle": "Образ жизни",
|
||||||
|
"male": "Мужской",
|
||||||
|
"female": "Женский",
|
||||||
|
"both": "Не важно",
|
||||||
|
"relationship": "Серьезные отношения",
|
||||||
|
"friendship": "Дружба",
|
||||||
|
"dating": "Свидания",
|
||||||
|
"hookup": "Интрижка",
|
||||||
|
"marriage": "Брак",
|
||||||
|
"networking": "Общение",
|
||||||
|
"travel": "Путешествия",
|
||||||
|
"business": "Бизнес",
|
||||||
|
"other": "Другое",
|
||||||
|
"cityNotSpecified": "Не указан",
|
||||||
|
"bioNotSpecified": "Описание не указано",
|
||||||
|
"interests": "Интересы",
|
||||||
|
"startSearch": "🔍 Начать поиск",
|
||||||
|
"noProfile": "❌ У вас пока нет профиля.\nСоздайте его для начала использования бота!",
|
||||||
|
"createFirst": "❌ Сначала создайте профиль!\nИспользуйте команду /start"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "Поиск анкет",
|
||||||
|
"noProfiles": "Анкеты закончились! Попробуйте позже.",
|
||||||
|
"like": "❤️ Нравится",
|
||||||
|
"dislike": "👎 Не нравится",
|
||||||
|
"superLike": "⭐ Супер лайк",
|
||||||
|
"match": "Это взаимность! 🎉"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "VIP Поиск",
|
||||||
|
"premiumRequired": "Функция доступна только для премиум пользователей",
|
||||||
|
"filters": "Фильтры",
|
||||||
|
"ageRange": "Возрастной диапазон",
|
||||||
|
"cityFilter": "Город",
|
||||||
|
"datingGoalFilter": "Цель знакомства",
|
||||||
|
"hobbiesFilter": "Хобби",
|
||||||
|
"lifestyleFilter": "Образ жизни",
|
||||||
|
"applyFilters": "Применить фильтры",
|
||||||
|
"clearFilters": "Очистить фильтры",
|
||||||
|
"noResults": "По вашим фильтрам никого не найдено",
|
||||||
|
"translateProfile": "🌐 Перевести анкету"
|
||||||
|
},
|
||||||
|
"premium": {
|
||||||
|
"title": "Премиум подписка",
|
||||||
|
"features": "Возможности премиум:",
|
||||||
|
"vipSearch": "• VIP поиск с фильтрами",
|
||||||
|
"profileTranslation": "• Перевод анкет на ваш язык",
|
||||||
|
"unlimitedLikes": "• Безлимитные лайки",
|
||||||
|
"superLikes": "• Дополнительные супер-лайки",
|
||||||
|
"price": "Стоимость: 299₽/месяц",
|
||||||
|
"activate": "Активировать премиум"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"translating": "Переводим анкету...",
|
||||||
|
"translated": "Анкета переведена:",
|
||||||
|
"error": "Ошибка перевода. Попробуйте позже.",
|
||||||
|
"premiumOnly": "Перевод доступен только для премиум пользователей"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "Главное меню",
|
||||||
|
"profile": "Моя анкета",
|
||||||
|
"search": "Поиск",
|
||||||
|
"vip": "VIP поиск",
|
||||||
|
"matches": "Взаимности",
|
||||||
|
"premium": "Премиум",
|
||||||
|
"settings": "Настройки",
|
||||||
|
"help": "Помощь"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« Назад",
|
||||||
|
"next": "Далее »",
|
||||||
|
"save": "Сохранить",
|
||||||
|
"cancel": "Отмена",
|
||||||
|
"confirm": "Подтвердить",
|
||||||
|
"edit": "Редактировать",
|
||||||
|
"delete": "Удалить",
|
||||||
|
"yes": "Да",
|
||||||
|
"no": "Нет"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Анкета не найдена",
|
||||||
|
"profileIncomplete": "Заполните анкету полностью",
|
||||||
|
"ageInvalid": "Введите корректный возраст (18-100)",
|
||||||
|
"photoRequired": "Добавьте хотя бы одну фотографию",
|
||||||
|
"networkError": "Ошибка сети. Попробуйте позже.",
|
||||||
|
"serverError": "Ошибка сервера. Попробуйте позже."
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"back": "👈 Назад"
|
||||||
|
},
|
||||||
|
"matches": {
|
||||||
|
"noMatches": "✨ У вас пока нет матчей.\n\n🔍 Попробуйте просмотреть больше анкет!\nИспользуйте /browse для поиска."
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"welcomeBack": "🎉 С возвращением, {name}!\n\n💖 Telegram Tinder Bot готов к работе!\n\nЧто хотите сделать?",
|
||||||
|
"welcomeNew": "🎉 Добро пожаловать в Telegram Tinder Bot!\n\n💕 Здесь вы можете найти свою вторую половинку!\n\nДля начала создайте свой профиль:",
|
||||||
|
"myProfile": "👤 Мой профиль",
|
||||||
|
"browseProfiles": "🔍 Просмотр анкет",
|
||||||
|
"myMatches": "💕 Мои матчи",
|
||||||
|
"vipSearch": "⭐ VIP поиск",
|
||||||
|
"settings": "⚙️ Настройки",
|
||||||
|
"createProfile": "🚀 Создать профиль",
|
||||||
|
"howItWorks": "ℹ️ Как это работает?"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"title": "🤖 Telegram Tinder Bot - Справка",
|
||||||
|
"description": "Бот для знакомств в Telegram\n\n📝 Создайте профиль\n🔍 Просматривайте анкеты\n❤️ Ставьте лайки\n💕 Находите взаимности\n💬 Общайтесь в чатах",
|
||||||
|
"commands": "📋 Команды:\n/start - Главное меню\n/profile - Мой профиль\n/browse - Просмотр анкет\n/matches - Мои матчи\n/settings - Настройки\n/help - Эта справка"
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/locales/ru_fixed.json
Normal file
94
src/locales/ru_fixed.json
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"commands": {
|
||||||
|
"start": "🏠 Главное меню",
|
||||||
|
"help": "ℹ️ Помощь",
|
||||||
|
"profile": "👤 Мой профиль",
|
||||||
|
"search": "🔍 Поиск анкет",
|
||||||
|
"matches": "💕 Матчи",
|
||||||
|
"premium": "⭐ Premium",
|
||||||
|
"settings": "⚙️ Настройки"
|
||||||
|
},
|
||||||
|
"menu": {
|
||||||
|
"main": "🏠 Главное меню",
|
||||||
|
"back": "👈 Назад",
|
||||||
|
"profile": "👤 Профиль",
|
||||||
|
"search": "🔍 Поиск",
|
||||||
|
"matches": "💕 Матчи",
|
||||||
|
"premium": "⭐ Premium",
|
||||||
|
"settings": "⚙️ Настройки"
|
||||||
|
},
|
||||||
|
"welcome": {
|
||||||
|
"newUser": "Добро пожаловать в Telegram Tinder Bot! 💕\n\nЗдесь вы сможете найти интересных людей для общения и знакомств.\n\nДля начала работы создайте свой профиль!",
|
||||||
|
"existingUser": "С возвращением! 👋\n\nВыберите действие:",
|
||||||
|
"createProfile": "🚀 Создать профиль"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"title": "📋 Как пользоваться ботом:",
|
||||||
|
"step1": "1️⃣ Создать профиль",
|
||||||
|
"step1Desc": " • Укажите имя, возраст, город\n • Добавьте описание\n • Загрузите фото",
|
||||||
|
"step2": "2️⃣ Просматривать анкеты",
|
||||||
|
"step2Desc": " • Листайте профили других пользователей\n • Ставьте лайки (❤️) или дизлайки (👎)",
|
||||||
|
"step3": "3️⃣ Получить матч",
|
||||||
|
"step3Desc": " • Когда два человека ставят лайки друг другу\n • Появляется возможность общения",
|
||||||
|
"step4": "4️⃣ Общение",
|
||||||
|
"step4Desc": " • Находите общие интересы\n • Договаривайтесь о встрече",
|
||||||
|
"tipsTitle": "💡 Советы:",
|
||||||
|
"tips": "• Используйте качественные фото\n• Напишите интересное описание\n• Будьте вежливы в общении",
|
||||||
|
"createProfile": "🚀 Создать профиль"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "⚙️ Настройки",
|
||||||
|
"language": "🌐 Язык интерфейса",
|
||||||
|
"ageRange": "📅 Возрастной диапазон",
|
||||||
|
"showAge": "🎂 Показывать возраст",
|
||||||
|
"showCity": "📍 Показывать город",
|
||||||
|
"notifications": "🔔 Уведомления",
|
||||||
|
"privacy": "🔒 Приватность",
|
||||||
|
"back": "👈 Назад"
|
||||||
|
},
|
||||||
|
"languages": {
|
||||||
|
"ru": "🇷🇺 Русский",
|
||||||
|
"en": "🇺🇸 English",
|
||||||
|
"es": "🇪🇸 Español",
|
||||||
|
"fr": "🇫🇷 Français",
|
||||||
|
"de": "🇩🇪 Deutsch",
|
||||||
|
"it": "🇮🇹 Italiano",
|
||||||
|
"pt": "🇵🇹 Português",
|
||||||
|
"zh": "🇨🇳 中文",
|
||||||
|
"ja": "🇯🇵 日本語",
|
||||||
|
"ko": "🇰🇷 한국어",
|
||||||
|
"uz": "🇺🇿 O'zbekcha",
|
||||||
|
"kk": "🇰🇿 Қазақша"
|
||||||
|
},
|
||||||
|
"howItWorks": {
|
||||||
|
"title": "🤔 Как это работает?",
|
||||||
|
"step1": "1️⃣ Создать профиль",
|
||||||
|
"step1Desc": " • Укажите имя, возраст, город\n • Добавьте описание\n • Загрузите фото",
|
||||||
|
"step2": "2️⃣ Просматривать анкеты",
|
||||||
|
"step2Desc": " • Листайте профили других пользователей\n • Ставьте лайки (❤️) или дизлайки (👎)",
|
||||||
|
"step3": "3️⃣ Получить матч",
|
||||||
|
"step3Desc": " • Когда два человека ставят лайки друг другу\n • Появляется возможность общения",
|
||||||
|
"step4": "4️⃣ Общение",
|
||||||
|
"step4Desc": " • Находите общие интересы\n • Договаривайтесь о встрече",
|
||||||
|
"tipsTitle": "💡 Советы:",
|
||||||
|
"tips": "• Используйте качественные фото\n• Напишите интересное описание\n• Будьте вежливы в общении",
|
||||||
|
"createProfile": "🚀 Создать профиль"
|
||||||
|
},
|
||||||
|
"noProfile": {
|
||||||
|
"message": "❌ У вас пока нет профиля.\\nСоздайте его для начала использования бота!",
|
||||||
|
"createButton": "🚀 Создать профиль"
|
||||||
|
},
|
||||||
|
"profileCreated": {
|
||||||
|
"success": "🎉 Профиль успешно создан!\\n\\nДобро пожаловать, {{name}}! 💖\\n\\nТеперь вы можете начать поиск своей второй половинки!",
|
||||||
|
"myProfile": "👤 Мой профиль",
|
||||||
|
"startSearch": "🔍 Начать поиск"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Анкета не найдена",
|
||||||
|
"profileIncomplete": "Заполните анкету полностью",
|
||||||
|
"ageInvalid": "Введите корректный возраст (18-100)",
|
||||||
|
"photoRequired": "Добавьте хотя бы одну фотографию",
|
||||||
|
"networkError": "Ошибка сети. Попробуйте позже.",
|
||||||
|
"serverError": "Ошибка сервера. Попробуйте позже."
|
||||||
|
}
|
||||||
|
}
|
||||||
152
src/locales/uz.json
Normal file
152
src/locales/uz.json
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "🎉 Telegram Tinder Botga xush kelibsiz!\n\n💕 Bu yerda siz o'zingizning hayot sherigigingizni topa olasiz!\n\nBoshlash uchun profilingizni yarating:",
|
||||||
|
"description": "O'zingizning hayot sherigigingizni shu yerda toping!",
|
||||||
|
"getStarted": "Tanishishni boshlash",
|
||||||
|
"haveProfile": "🎉 Xush kelibsiz, {{name}}!\n\n💖 Telegram Tinder Bot ishga tayyor!\n\nNima qilmoqchisiz?"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "Profil yaratish",
|
||||||
|
"edit": "Profilni tahrirlash",
|
||||||
|
"view": "Profilni ko'rish",
|
||||||
|
"name": "Ism",
|
||||||
|
"age": "Yosh",
|
||||||
|
"city": "Shahar",
|
||||||
|
"bio": "O'zim haqimda",
|
||||||
|
"photos": "Rasmlar",
|
||||||
|
"gender": "Jins",
|
||||||
|
"lookingFor": "Qidiraman",
|
||||||
|
"datingGoal": "Tanishuv maqsadi",
|
||||||
|
"hobbies": "Sevimli mashg'ulotlar",
|
||||||
|
"lifestyle": "Turmush tarzi",
|
||||||
|
"male": "Erkak",
|
||||||
|
"female": "Ayol",
|
||||||
|
"both": "Muhim emas",
|
||||||
|
"relationship": "Jiddiy munosabatlar",
|
||||||
|
"friendship": "Do'stlik",
|
||||||
|
"dating": "Uchrashuvlar",
|
||||||
|
"hookup": "Qisqa munosabat",
|
||||||
|
"marriage": "Nikoh",
|
||||||
|
"networking": "Muloqot",
|
||||||
|
"travel": "Sayohat",
|
||||||
|
"business": "Biznes",
|
||||||
|
"other": "Boshqa"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "Profillarni qidirish",
|
||||||
|
"noProfiles": "Profillar tugadi! Keyinroq urinib ko'ring.",
|
||||||
|
"like": "👍 Yoqadi",
|
||||||
|
"dislike": "👎 Yoqmadi",
|
||||||
|
"superlike": "💖 Super yoqdi",
|
||||||
|
"match": "Bu o'zaro yoqish! 🎉",
|
||||||
|
"tryAgain": "🔄 Yana urinish",
|
||||||
|
"myMatches": "💕 Mening matchlarim",
|
||||||
|
"allViewed": "🎉 Siz barcha mavjud nomzodlarni ko'rib chiqdingiz!\n\n⏰ Keyinroq urinib ko'ring - yangi profillar paydo bo'lishi mumkin!",
|
||||||
|
"viewProfile": "👤 Profil",
|
||||||
|
"morePhotos": "📸 Yana rasmlar",
|
||||||
|
"next": "⏭ Keyingi",
|
||||||
|
"sendMessage": "💬 Xabar yozish",
|
||||||
|
"continueBrowsing": "🔍 Qidirishni davom ettirish",
|
||||||
|
"matchFound": "🎉 BU MATCH! 💕\n\n{{name}} bilan o'zaro yoqdingiz!\n\nEndi suhbatni boshlashingiz mumkin!",
|
||||||
|
"noMoreProfiles": "😔 Hozircha yangi profillar yo'q.\n\n⏰ Keyinroq qaytib kelishingiz mumkin!"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "⭐ VIP Qidiruv",
|
||||||
|
"description": "Premium imkoniyatlar bilan qidiring!",
|
||||||
|
"features": "• Cheksiz yoqish\n• Super yoqishlar\n• Kimlar yoqganini ko'rish\n• Reklamasiz tajriba",
|
||||||
|
"getVip": "VIP olish",
|
||||||
|
"alreadyVip": "Siz allaqachon VIP foydalanuvchisiz!"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"inProgress": "🔄 Tarjima qilinmoqda...",
|
||||||
|
"completed": "✅ Tarjima tayyor!",
|
||||||
|
"failed": "❌ Tarjima xatosi",
|
||||||
|
"error": "Tarjima xatosi. Keyinroq urinib ko'ring.",
|
||||||
|
"premiumOnly": "Tarjima faqat premium foydalanuvchilar uchun"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "Bosh menyu",
|
||||||
|
"profile": "Mening profilim",
|
||||||
|
"search": "Qidiruv",
|
||||||
|
"vip": "VIP qidiruv",
|
||||||
|
"matches": "O'zaro yoqishlar",
|
||||||
|
"premium": "Premium",
|
||||||
|
"settings": "Sozlamalar",
|
||||||
|
"help": "Yordam"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« Orqaga",
|
||||||
|
"next": "Keyingi »",
|
||||||
|
"save": "Saqlash",
|
||||||
|
"cancel": "Bekor qilish",
|
||||||
|
"confirm": "Tasdiqlash",
|
||||||
|
"edit": "Tahrirlash",
|
||||||
|
"delete": "O'chirish",
|
||||||
|
"yes": "Ha",
|
||||||
|
"no": "Yo'q"
|
||||||
|
},
|
||||||
|
"help": {
|
||||||
|
"title": "🤖 Telegram Tinder Bot - Yordam",
|
||||||
|
"commands": "📋 Mavjud buyruqlar:",
|
||||||
|
"commandStart": "/start - Bosh menyu",
|
||||||
|
"commandProfile": "/profile - Profil boshqaruvi",
|
||||||
|
"commandBrowse": "/browse - Profillarni ko'rish",
|
||||||
|
"commandMatches": "/matches - Sizning matchlaringiz",
|
||||||
|
"commandSettings": "/settings - Sozlamalar",
|
||||||
|
"commandHelp": "/help - Ushbu yordam",
|
||||||
|
"howToUse": "📱 Qanday foydalanish:",
|
||||||
|
"step1": "1. Rasm va tavsif bilan profil yarating",
|
||||||
|
"step2": "2. Boshqa foydalanuvchilarning profillarini ko'ring",
|
||||||
|
"step3": "3. Yoqganlaringizga yoqish bosing",
|
||||||
|
"step4": "4. O'zaro yoqganlar bilan suhbatlashing!",
|
||||||
|
"goodLuck": "❤️ Sevgi topishda omad tilaymiz!"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "⚙️ Profil sozlamalari\n\nO'zgartirmoqchi bo'lgan narsani tanlang:",
|
||||||
|
"searchSettings": "🔍 Qidiruv sozlamalari",
|
||||||
|
"notifications": "🔔 Bildirishnomalar",
|
||||||
|
"language": "🌐 Interfeys tili",
|
||||||
|
"stats": "📊 Statistika",
|
||||||
|
"hideProfile": "🚫 Profilni yashirish",
|
||||||
|
"deleteProfile": "🗑 Profilni o'chirish",
|
||||||
|
"searchComingSoon": "🔍 Qidiruv sozlamalari keyingi yangilanishda bo'ladi!",
|
||||||
|
"notificationsComingSoon": "🔔 Bildirishnoma sozlamalari keyingi yangilanishda bo'ladi!"
|
||||||
|
},
|
||||||
|
"howItWorks": {
|
||||||
|
"title": "🎯 Telegram Tinder Bot qanday ishlaydi?",
|
||||||
|
"step1Title": "1️⃣ Profil yarating",
|
||||||
|
"step1Desc": " • Rasm va tavsif qo'shing\n • O'zingizning xohishlaringizni belgilang",
|
||||||
|
"step2Title": "2️⃣ Profillarni ko'ring",
|
||||||
|
"step2Desc": " • Yoqganlaringizga yoqish bosing\n • Maxsus holatlar uchun super yoqish ishlating",
|
||||||
|
"step3Title": "3️⃣ Matchlar oling",
|
||||||
|
"step3Desc": " • Yoqishingiz o'zaro bo'lsa - bu match!\n • Suhbatni boshlang",
|
||||||
|
"step4Title": "4️⃣ Suhbatlashing va tanishing",
|
||||||
|
"step4Desc": " • Umumiy qiziqishlarni toping\n • Uchrashuvni rejalang",
|
||||||
|
"tipsTitle": "💡 Maslahatlar:",
|
||||||
|
"tips": "• Sifatli rasmlar ishlating\n• Qiziqarli tavsif yozing\n• Suhbatda xushmuomala bo'ling",
|
||||||
|
"createProfile": "🚀 Profil yaratish"
|
||||||
|
},
|
||||||
|
"noProfile": {
|
||||||
|
"message": "❌ Sizda hali profil yo'q.\\nBotdan foydalanish uchun profil yarating!",
|
||||||
|
"createButton": "🚀 Profil yaratish"
|
||||||
|
},
|
||||||
|
"noMatches": {
|
||||||
|
"message": "💔 Sizda hali matchlar yo'q.\\n\\n🔍 Ko'proq profillarni ko'rib chiqing!\\nQidiruv uchun /browse dan foydalaning."
|
||||||
|
},
|
||||||
|
"browsing": {
|
||||||
|
"needProfile": "❌ Avval profil yarating!\\n/start buyrug'idan foydalaning"
|
||||||
|
},
|
||||||
|
"profileCreated": {
|
||||||
|
"success": "🎉 Profil muvaffaqiyatli yaratildi!\n\nXush kelibsiz, {{name}}! 💖\n\nEndi siz o'zingizning hayot sherigigingizni qidirishni boshlashingiz mumkin!",
|
||||||
|
"myProfile": "👤 Mening profilim",
|
||||||
|
"startSearch": "🔍 Qidirishni boshlash"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "Profil topilmadi",
|
||||||
|
"profileIncomplete": "Profilni to'liq to'ldiring",
|
||||||
|
"ageInvalid": "To'g'ri yoshni kiriting (18-100)",
|
||||||
|
"photoRequired": "Kamida bitta rasm qo'shing",
|
||||||
|
"networkError": "Tarmoq xatosi. Keyinroq urinib ko'ring.",
|
||||||
|
"serverError": "Server xatosi. Keyinroq urinib ko'ring."
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/locales/zh.json
Normal file
101
src/locales/zh.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"welcome": {
|
||||||
|
"greeting": "欢迎使用Telegram Tinder机器人!💕",
|
||||||
|
"description": "在这里找到你的灵魂伴侣!",
|
||||||
|
"getStarted": "开始"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"create": "创建资料",
|
||||||
|
"edit": "编辑资料",
|
||||||
|
"view": "查看资料",
|
||||||
|
"name": "姓名",
|
||||||
|
"age": "年龄",
|
||||||
|
"city": "城市",
|
||||||
|
"bio": "关于我",
|
||||||
|
"photos": "照片",
|
||||||
|
"gender": "性别",
|
||||||
|
"lookingFor": "寻找",
|
||||||
|
"datingGoal": "约会目的",
|
||||||
|
"hobbies": "爱好",
|
||||||
|
"lifestyle": "生活方式",
|
||||||
|
"male": "男性",
|
||||||
|
"female": "女性",
|
||||||
|
"both": "都可以",
|
||||||
|
"relationship": "恋爱关系",
|
||||||
|
"friendship": "友谊",
|
||||||
|
"dating": "约会",
|
||||||
|
"hookup": "随意交往",
|
||||||
|
"marriage": "结婚",
|
||||||
|
"networking": "社交",
|
||||||
|
"travel": "旅行",
|
||||||
|
"business": "商务",
|
||||||
|
"other": "其他"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"title": "浏览资料",
|
||||||
|
"noProfiles": "没有更多资料了!请稍后再试。",
|
||||||
|
"like": "❤️ 喜欢",
|
||||||
|
"dislike": "👎 跳过",
|
||||||
|
"superLike": "⭐ 超级喜欢",
|
||||||
|
"match": "配对成功!🎉"
|
||||||
|
},
|
||||||
|
"vip": {
|
||||||
|
"title": "VIP搜索",
|
||||||
|
"premiumRequired": "此功能仅对高级用户开放",
|
||||||
|
"filters": "筛选器",
|
||||||
|
"ageRange": "年龄范围",
|
||||||
|
"cityFilter": "城市",
|
||||||
|
"datingGoalFilter": "约会目的",
|
||||||
|
"hobbiesFilter": "爱好",
|
||||||
|
"lifestyleFilter": "生活方式",
|
||||||
|
"applyFilters": "应用筛选器",
|
||||||
|
"clearFilters": "清除筛选器",
|
||||||
|
"noResults": "没有找到符合您筛选条件的资料",
|
||||||
|
"translateProfile": "🌐 翻译资料"
|
||||||
|
},
|
||||||
|
"premium": {
|
||||||
|
"title": "高级订阅",
|
||||||
|
"features": "高级功能:",
|
||||||
|
"vipSearch": "• 带筛选器的VIP搜索",
|
||||||
|
"profileTranslation": "• 将资料翻译成您的语言",
|
||||||
|
"unlimitedLikes": "• 无限点赞",
|
||||||
|
"superLikes": "• 额外的超级喜欢",
|
||||||
|
"price": "价格:¥35/月",
|
||||||
|
"activate": "激活高级版"
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"translating": "正在翻译资料...",
|
||||||
|
"translated": "已翻译的资料:",
|
||||||
|
"error": "翻译错误。请稍后再试。",
|
||||||
|
"premiumOnly": "翻译功能仅对高级用户开放"
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"start": "主菜单",
|
||||||
|
"profile": "我的资料",
|
||||||
|
"search": "浏览",
|
||||||
|
"vip": "VIP搜索",
|
||||||
|
"matches": "配对",
|
||||||
|
"premium": "高级版",
|
||||||
|
"settings": "设置",
|
||||||
|
"help": "帮助"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"back": "« 返回",
|
||||||
|
"next": "下一步 »",
|
||||||
|
"save": "保存",
|
||||||
|
"cancel": "取消",
|
||||||
|
"confirm": "确认",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除",
|
||||||
|
"yes": "是",
|
||||||
|
"no": "否"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"profileNotFound": "未找到资料",
|
||||||
|
"profileIncomplete": "请完善您的资料",
|
||||||
|
"ageInvalid": "请输入有效年龄(18-100)",
|
||||||
|
"photoRequired": "请至少添加一张照片",
|
||||||
|
"networkError": "网络错误。请稍后再试。",
|
||||||
|
"serverError": "服务器错误。请稍后再试。"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ export interface UserData {
|
|||||||
firstName?: string;
|
firstName?: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
languageCode?: string;
|
languageCode?: string;
|
||||||
|
language?: string; // Предпочитаемый язык интерфейса
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
lastActiveAt: Date;
|
lastActiveAt: Date;
|
||||||
@@ -17,6 +18,7 @@ export class User {
|
|||||||
firstName?: string;
|
firstName?: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
languageCode?: string;
|
languageCode?: string;
|
||||||
|
language: string; // Предпочитаемый язык интерфейса
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
lastActiveAt: Date;
|
lastActiveAt: Date;
|
||||||
@@ -28,6 +30,7 @@ export class User {
|
|||||||
this.firstName = data.firstName;
|
this.firstName = data.firstName;
|
||||||
this.lastName = data.lastName;
|
this.lastName = data.lastName;
|
||||||
this.languageCode = data.languageCode || 'en';
|
this.languageCode = data.languageCode || 'en';
|
||||||
|
this.language = data.language || 'ru'; // Язык интерфейса по умолчанию
|
||||||
this.isActive = data.isActive;
|
this.isActive = data.isActive;
|
||||||
this.createdAt = data.createdAt;
|
this.createdAt = data.createdAt;
|
||||||
this.lastActiveAt = data.lastActiveAt;
|
this.lastActiveAt = data.lastActiveAt;
|
||||||
@@ -67,4 +70,14 @@ export class User {
|
|||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
this.updateLastActive();
|
this.updateLastActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Установить язык интерфейса
|
||||||
|
setLanguage(language: string): void {
|
||||||
|
this.language = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получить язык интерфейса
|
||||||
|
getLanguage(): string {
|
||||||
|
return this.language;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
171
src/services/deepSeekTranslationService.ts
Normal file
171
src/services/deepSeekTranslationService.ts
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export interface TranslationRequest {
|
||||||
|
text: string;
|
||||||
|
targetLanguage: string;
|
||||||
|
sourceLanguage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TranslationResponse {
|
||||||
|
translatedText: string;
|
||||||
|
sourceLanguage: string;
|
||||||
|
targetLanguage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DeepSeekTranslationService {
|
||||||
|
private static instance: DeepSeekTranslationService;
|
||||||
|
private apiKey: string;
|
||||||
|
private apiUrl: string = 'https://api.deepseek.com/v1/chat/completions';
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.apiKey = process.env.DEEPSEEK_API_KEY || '';
|
||||||
|
if (!this.apiKey) {
|
||||||
|
console.warn('⚠️ DEEPSEEK_API_KEY not found in environment variables');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): DeepSeekTranslationService {
|
||||||
|
if (!DeepSeekTranslationService.instance) {
|
||||||
|
DeepSeekTranslationService.instance = new DeepSeekTranslationService();
|
||||||
|
}
|
||||||
|
return DeepSeekTranslationService.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async translateProfile(request: TranslationRequest): Promise<TranslationResponse> {
|
||||||
|
if (!this.apiKey) {
|
||||||
|
throw new Error('DeepSeek API key is not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const prompt = this.createTranslationPrompt(request);
|
||||||
|
|
||||||
|
const response = await axios.post(this.apiUrl, {
|
||||||
|
model: 'deepseek-chat',
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: 'You are a professional translator specializing in dating profiles. Translate the given text naturally, preserving the tone and personality. Respond only with the translated text, no additional comments.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: prompt
|
||||||
|
}
|
||||||
|
],
|
||||||
|
max_tokens: 1000,
|
||||||
|
temperature: 0.3
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${this.apiKey}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
timeout: 30000 // 30 секунд таймаут
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data?.choices?.[0]?.message?.content) {
|
||||||
|
const translatedText = response.data.choices[0].message.content.trim();
|
||||||
|
|
||||||
|
return {
|
||||||
|
translatedText,
|
||||||
|
sourceLanguage: request.sourceLanguage || 'auto',
|
||||||
|
targetLanguage: request.targetLanguage
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid response from DeepSeek API');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ DeepSeek translation error:', error);
|
||||||
|
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
throw new Error('Invalid DeepSeek API key');
|
||||||
|
} else if (error.response?.status === 429) {
|
||||||
|
throw new Error('Translation rate limit exceeded. Please try again later.');
|
||||||
|
} else if (error.code === 'ECONNABORTED') {
|
||||||
|
throw new Error('Translation request timed out. Please try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Translation service temporarily unavailable');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createTranslationPrompt(request: TranslationRequest): string {
|
||||||
|
const languageMap: { [key: string]: string } = {
|
||||||
|
'en': 'English',
|
||||||
|
'ru': 'Russian',
|
||||||
|
'es': 'Spanish',
|
||||||
|
'fr': 'French',
|
||||||
|
'de': 'German',
|
||||||
|
'it': 'Italian',
|
||||||
|
'pt': 'Portuguese',
|
||||||
|
'zh': 'Chinese',
|
||||||
|
'ja': 'Japanese',
|
||||||
|
'ko': 'Korean'
|
||||||
|
};
|
||||||
|
|
||||||
|
const targetLanguageName = languageMap[request.targetLanguage] || request.targetLanguage;
|
||||||
|
|
||||||
|
let prompt = `Translate the following dating profile text to ${targetLanguageName}. `;
|
||||||
|
|
||||||
|
if (request.sourceLanguage && request.sourceLanguage !== 'auto') {
|
||||||
|
const sourceLanguageName = languageMap[request.sourceLanguage] || request.sourceLanguage;
|
||||||
|
prompt += `The source language is ${sourceLanguageName}. `;
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt += `Keep the tone natural and personal, as if the person is describing themselves:\n\n${request.text}`;
|
||||||
|
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определить язык текста (базовая логика)
|
||||||
|
public detectLanguage(text: string): string {
|
||||||
|
// Простая эвристика для определения языка
|
||||||
|
const cyrillicPattern = /[а-яё]/i;
|
||||||
|
const latinPattern = /[a-z]/i;
|
||||||
|
|
||||||
|
const cyrillicCount = (text.match(cyrillicPattern) || []).length;
|
||||||
|
const latinCount = (text.match(latinPattern) || []).length;
|
||||||
|
|
||||||
|
if (cyrillicCount > latinCount) {
|
||||||
|
return 'ru';
|
||||||
|
} else if (latinCount > 0) {
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверить доступность сервиса
|
||||||
|
public async checkServiceAvailability(): Promise<boolean> {
|
||||||
|
if (!this.apiKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(this.apiUrl, {
|
||||||
|
model: 'deepseek-chat',
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: 'Test'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
max_tokens: 5
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${this.apiKey}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
timeout: 10000
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.status === 200;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('DeepSeek service availability check failed:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeepSeekTranslationService;
|
||||||
154
src/services/localizationService.ts
Normal file
154
src/services/localizationService.ts
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import i18next from 'i18next';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { pool } from '../database/connection';
|
||||||
|
|
||||||
|
export class LocalizationService {
|
||||||
|
private static instance: LocalizationService;
|
||||||
|
private initialized = false;
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
public static getInstance(): LocalizationService {
|
||||||
|
if (!LocalizationService.instance) {
|
||||||
|
LocalizationService.instance = new LocalizationService();
|
||||||
|
}
|
||||||
|
return LocalizationService.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async initialize(): Promise<void> {
|
||||||
|
if (this.initialized) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Загружаем файлы переводов
|
||||||
|
const localesPath = path.join(__dirname, '..', 'locales');
|
||||||
|
const ruTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'ru.json'), 'utf8'));
|
||||||
|
const enTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'en.json'), 'utf8'));
|
||||||
|
const esTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'es.json'), 'utf8'));
|
||||||
|
const frTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'fr.json'), 'utf8'));
|
||||||
|
const deTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'de.json'), 'utf8'));
|
||||||
|
const itTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'it.json'), 'utf8'));
|
||||||
|
const ptTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'pt.json'), 'utf8'));
|
||||||
|
const zhTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'zh.json'), 'utf8'));
|
||||||
|
const jaTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'ja.json'), 'utf8'));
|
||||||
|
const koTranslations = JSON.parse(fs.readFileSync(path.join(localesPath, 'ko.json'), 'utf8'));
|
||||||
|
|
||||||
|
await i18next.init({
|
||||||
|
lng: 'ru', // Язык по умолчанию
|
||||||
|
fallbackLng: 'ru',
|
||||||
|
debug: false,
|
||||||
|
resources: {
|
||||||
|
ru: {
|
||||||
|
translation: ruTranslations
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
translation: enTranslations
|
||||||
|
},
|
||||||
|
es: {
|
||||||
|
translation: esTranslations
|
||||||
|
},
|
||||||
|
fr: {
|
||||||
|
translation: frTranslations
|
||||||
|
},
|
||||||
|
de: {
|
||||||
|
translation: deTranslations
|
||||||
|
},
|
||||||
|
it: {
|
||||||
|
translation: itTranslations
|
||||||
|
},
|
||||||
|
pt: {
|
||||||
|
translation: ptTranslations
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
translation: zhTranslations
|
||||||
|
},
|
||||||
|
ja: {
|
||||||
|
translation: jaTranslations
|
||||||
|
},
|
||||||
|
ko: {
|
||||||
|
translation: koTranslations
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
console.log('✅ Localization service initialized successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to initialize localization service:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public t(key: string, options?: any): string {
|
||||||
|
return i18next.t(key, options) as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLanguage(language: string): void {
|
||||||
|
i18next.changeLanguage(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentLanguage(): string {
|
||||||
|
return i18next.language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSupportedLanguages(): string[] {
|
||||||
|
return ['ru', 'en', 'es', 'fr', 'de', 'it', 'pt', 'zh', 'ja', 'ko'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получить перевод для определенного языка без изменения текущего
|
||||||
|
public getTranslation(key: string, language: string, options?: any): string {
|
||||||
|
const currentLang = i18next.language;
|
||||||
|
i18next.changeLanguage(language);
|
||||||
|
const translation = i18next.t(key, options) as string;
|
||||||
|
i18next.changeLanguage(currentLang);
|
||||||
|
return translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определить язык пользователя по его настройкам Telegram
|
||||||
|
public detectUserLanguage(telegramLanguageCode?: string): string {
|
||||||
|
if (!telegramLanguageCode) return 'ru';
|
||||||
|
|
||||||
|
// Поддерживаемые языки
|
||||||
|
const supportedLanguages = this.getSupportedLanguages();
|
||||||
|
|
||||||
|
// Проверяем точное совпадение
|
||||||
|
if (supportedLanguages.includes(telegramLanguageCode)) {
|
||||||
|
return telegramLanguageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем по первым двум символам (например, en-US -> en)
|
||||||
|
const languagePrefix = telegramLanguageCode.substring(0, 2);
|
||||||
|
if (supportedLanguages.includes(languagePrefix)) {
|
||||||
|
return languagePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
// По умолчанию русский
|
||||||
|
return 'ru';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для получения персонализированного перевода пользователя
|
||||||
|
export const getUserTranslation = async (telegramId: string, key: string, options?: any): Promise<string> => {
|
||||||
|
try {
|
||||||
|
// Получаем язык пользователя из базы данных
|
||||||
|
const result = await pool.query('SELECT language FROM users WHERE telegram_id = $1', [telegramId]);
|
||||||
|
const userLanguage = result.rows[0]?.language || 'ru';
|
||||||
|
|
||||||
|
// Получаем перевод для языка пользователя
|
||||||
|
return LocalizationService.getInstance().getTranslation(key, userLanguage, options);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting user translation:', error);
|
||||||
|
// Возвращаем перевод на русском языке по умолчанию
|
||||||
|
return LocalizationService.getInstance().getTranslation(key, 'ru', options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция-хелпер для быстрого доступа к переводам
|
||||||
|
export const t = (key: string, options?: any): string => {
|
||||||
|
return LocalizationService.getInstance().t(key, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LocalizationService;
|
||||||
257
src/services/vipService.ts
Normal file
257
src/services/vipService.ts
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
import { query } from '../database/connection';
|
||||||
|
import { BotError } from '../types/index';
|
||||||
|
|
||||||
|
export interface VipSearchFilters {
|
||||||
|
ageMin?: number;
|
||||||
|
ageMax?: number;
|
||||||
|
city?: string;
|
||||||
|
datingGoal?: string;
|
||||||
|
hobbies?: string[];
|
||||||
|
lifestyle?: string[];
|
||||||
|
distance?: number;
|
||||||
|
hasPhotos?: boolean;
|
||||||
|
isOnline?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PremiumInfo {
|
||||||
|
isPremium: boolean;
|
||||||
|
expiresAt?: Date;
|
||||||
|
daysLeft?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VipService {
|
||||||
|
|
||||||
|
// Проверить премиум статус пользователя
|
||||||
|
async checkPremiumStatus(telegramId: string): Promise<PremiumInfo> {
|
||||||
|
try {
|
||||||
|
const result = await query(`
|
||||||
|
SELECT premium, premium_expires_at
|
||||||
|
FROM users
|
||||||
|
WHERE telegram_id = $1
|
||||||
|
`, [telegramId]);
|
||||||
|
|
||||||
|
if (result.rows.length === 0) {
|
||||||
|
throw new BotError('User not found', 'USER_NOT_FOUND', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = result.rows[0];
|
||||||
|
const isPremium = user.premium;
|
||||||
|
const expiresAt = user.premium_expires_at ? new Date(user.premium_expires_at) : undefined;
|
||||||
|
|
||||||
|
let daysLeft = undefined;
|
||||||
|
if (isPremium && expiresAt) {
|
||||||
|
const now = new Date();
|
||||||
|
const timeDiff = expiresAt.getTime() - now.getTime();
|
||||||
|
daysLeft = Math.ceil(timeDiff / (1000 * 3600 * 24));
|
||||||
|
|
||||||
|
// Если премиум истек
|
||||||
|
if (daysLeft <= 0) {
|
||||||
|
await this.removePremium(telegramId);
|
||||||
|
return { isPremium: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isPremium,
|
||||||
|
expiresAt,
|
||||||
|
daysLeft
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking premium status:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавить премиум статус
|
||||||
|
async addPremium(telegramId: string, durationDays: number = 30): Promise<void> {
|
||||||
|
try {
|
||||||
|
const expiresAt = new Date();
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + durationDays);
|
||||||
|
|
||||||
|
await query(`
|
||||||
|
UPDATE users
|
||||||
|
SET premium = true, premium_expires_at = $2
|
||||||
|
WHERE telegram_id = $1
|
||||||
|
`, [telegramId, expiresAt]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error adding premium:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удалить премиум статус
|
||||||
|
async removePremium(telegramId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await query(`
|
||||||
|
UPDATE users
|
||||||
|
SET premium = false, premium_expires_at = NULL
|
||||||
|
WHERE telegram_id = $1
|
||||||
|
`, [telegramId]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error removing premium:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIP поиск с фильтрами
|
||||||
|
async vipSearch(telegramId: string, filters: VipSearchFilters): Promise<any[]> {
|
||||||
|
try {
|
||||||
|
// Проверяем премиум статус
|
||||||
|
const premiumInfo = await this.checkPremiumStatus(telegramId);
|
||||||
|
if (!premiumInfo.isPremium) {
|
||||||
|
throw new BotError('Premium subscription required', 'PREMIUM_REQUIRED', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем профиль пользователя
|
||||||
|
const userProfile = await query(`
|
||||||
|
SELECT p.*, u.telegram_id
|
||||||
|
FROM profiles p
|
||||||
|
JOIN users u ON p.user_id = u.id
|
||||||
|
WHERE u.telegram_id = $1
|
||||||
|
`, [telegramId]);
|
||||||
|
|
||||||
|
if (userProfile.rows.length === 0) {
|
||||||
|
throw new BotError('Profile not found', 'PROFILE_NOT_FOUND', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUser = userProfile.rows[0];
|
||||||
|
|
||||||
|
// Строим запрос с фильтрами
|
||||||
|
let query_text = `
|
||||||
|
SELECT p.*, u.telegram_id,
|
||||||
|
CASE WHEN u.updated_at > NOW() - INTERVAL '15 minutes' THEN true ELSE false END as is_online
|
||||||
|
FROM profiles p
|
||||||
|
JOIN users u ON p.user_id = u.id
|
||||||
|
LEFT JOIN swipes s ON (
|
||||||
|
s.swiper_id = $1 AND s.swiped_id = u.id
|
||||||
|
)
|
||||||
|
WHERE u.telegram_id != $2
|
||||||
|
AND s.id IS NULL
|
||||||
|
AND p.is_active = true
|
||||||
|
`;
|
||||||
|
|
||||||
|
let params = [currentUser.user_id, telegramId];
|
||||||
|
let paramIndex = 3;
|
||||||
|
|
||||||
|
// Фильтр по противоположному полу
|
||||||
|
if (currentUser.gender === 'male') {
|
||||||
|
query_text += ` AND p.gender = 'female'`;
|
||||||
|
} else if (currentUser.gender === 'female') {
|
||||||
|
query_text += ` AND p.gender = 'male'`;
|
||||||
|
} else {
|
||||||
|
// Если пол не определен или 'other', показываем всех кроме того же пола
|
||||||
|
query_text += ` AND p.gender != $${paramIndex}`;
|
||||||
|
params.push(currentUser.gender);
|
||||||
|
paramIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтр по возрасту
|
||||||
|
if (filters.ageMin) {
|
||||||
|
query_text += ` AND p.age >= $${paramIndex}`;
|
||||||
|
params.push(filters.ageMin);
|
||||||
|
paramIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.ageMax) {
|
||||||
|
query_text += ` AND p.age <= $${paramIndex}`;
|
||||||
|
params.push(filters.ageMax);
|
||||||
|
paramIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтр по городу
|
||||||
|
if (filters.city) {
|
||||||
|
query_text += ` AND LOWER(p.city) LIKE LOWER($${paramIndex})`;
|
||||||
|
params.push(`%${filters.city}%`);
|
||||||
|
paramIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтр по цели знакомства
|
||||||
|
if (filters.datingGoal) {
|
||||||
|
query_text += ` AND p.dating_goal = $${paramIndex}`;
|
||||||
|
params.push(filters.datingGoal);
|
||||||
|
paramIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтр по хобби
|
||||||
|
if (filters.hobbies && filters.hobbies.length > 0) {
|
||||||
|
const hobbyConditions = filters.hobbies.map((_, index) => {
|
||||||
|
return `LOWER(p.hobbies) LIKE LOWER($${paramIndex + index})`;
|
||||||
|
});
|
||||||
|
query_text += ` AND (${hobbyConditions.join(' OR ')})`;
|
||||||
|
filters.hobbies.forEach(hobby => {
|
||||||
|
params.push(`%${hobby}%`);
|
||||||
|
});
|
||||||
|
paramIndex += filters.hobbies.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтр по образу жизни
|
||||||
|
if (filters.lifestyle && filters.lifestyle.length > 0) {
|
||||||
|
const lifestyleConditions = filters.lifestyle.map((field) => {
|
||||||
|
const condition = `p.lifestyle ? $${paramIndex}`;
|
||||||
|
params.push(field);
|
||||||
|
paramIndex++;
|
||||||
|
return condition;
|
||||||
|
});
|
||||||
|
query_text += ` AND (${lifestyleConditions.join(' OR ')})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтр по наличию фото
|
||||||
|
if (filters.hasPhotos) {
|
||||||
|
query_text += ` AND p.photos IS NOT NULL AND array_length(p.photos, 1) > 0`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Фильтр по онлайн статусу
|
||||||
|
if (filters.isOnline) {
|
||||||
|
query_text += ` AND u.updated_at > NOW() - INTERVAL '15 minutes'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
query_text += ` ORDER BY
|
||||||
|
CASE WHEN u.updated_at > NOW() - INTERVAL '15 minutes' THEN 0 ELSE 1 END,
|
||||||
|
u.updated_at DESC,
|
||||||
|
p.created_at DESC
|
||||||
|
LIMIT 50`;
|
||||||
|
|
||||||
|
const result = await query(query_text, params);
|
||||||
|
return result.rows;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in VIP search:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получить информацию о премиум возможностях
|
||||||
|
getPremiumFeatures(): string {
|
||||||
|
return `💎 ПРЕМИУМ ПОДПИСКА 💎
|
||||||
|
|
||||||
|
🔥 Что дает VIP статус:
|
||||||
|
|
||||||
|
🎯 VIP Поиск с фильтрами:
|
||||||
|
• Поиск по возрасту
|
||||||
|
• Поиск по городу
|
||||||
|
• Фильтр по целям знакомства
|
||||||
|
• Поиск по хобби и интересам
|
||||||
|
• Фильтр по образу жизни
|
||||||
|
• Только пользователи с фото
|
||||||
|
• Только онлайн пользователи
|
||||||
|
|
||||||
|
⚡ Дополнительные возможности:
|
||||||
|
• Неограниченные супер-лайки
|
||||||
|
• Просмотр кто лайкнул вас
|
||||||
|
• Возможность отменить свайп
|
||||||
|
• Приоритет в показе другим
|
||||||
|
• Расширенная статистика
|
||||||
|
• Скрытый режим просмотра
|
||||||
|
|
||||||
|
💰 Тарифы:
|
||||||
|
• 1 месяц - 299₽
|
||||||
|
• 3 месяца - 699₽ (экономия 25%)
|
||||||
|
• 6 месяцев - 1199₽ (экономия 33%)
|
||||||
|
• 1 год - 1999₽ (экономия 44%)
|
||||||
|
|
||||||
|
📞 Для покупки обратитесь к администратору:
|
||||||
|
@admin_bot
|
||||||
|
|
||||||
|
✨ Попробуйте VIP уже сегодня!`;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user