Enhance referral program features and localization updates

- Implemented referral statistics retrieval to provide users with insights on invited friends and subscription purchases.
- Updated referral command handler to include new statistics in the response message, improving user engagement.
- Revised localization files for both English and Russian to reflect changes in the referral program messaging, enhancing clarity and user experience.
This commit is contained in:
machka-pasla
2025-08-03 22:27:41 +03:00
parent b13883c039
commit ce041758f1
6 changed files with 183 additions and 29 deletions
+120 -11
View File
@@ -805,15 +805,35 @@ async def process_promo_edit_max_activations_handler(message: types.Message, sta
await state.update_data(new_max_activations=max_activations) await state.update_data(new_max_activations=max_activations)
# Step 4: Ask for validity days # Step 4: Ask for validity days (with buttons)
current_valid_until = data.get("original_valid_until") current_valid_until = data.get("original_valid_until")
current_validity_str = _("admin_promo_valid_indefinitely", default="Неограниченно") current_validity_str = _("admin_promo_valid_indefinitely", default="Неограниченно")
if current_valid_until: if current_valid_until:
current_validity_str = current_valid_until.strftime('%Y-%m-%d') current_validity_str = current_valid_until.strftime('%Y-%m-%d')
keyboard = InlineKeyboardBuilder()
keyboard.row(
InlineKeyboardButton(
text=_("admin_promo_edit_unlimited_validity", default="♾️ Неограниченно"),
callback_data="promo_edit_unlimited_validity"
)
)
keyboard.row(
InlineKeyboardButton(
text=_("admin_promo_edit_set_validity", default="⏰ Установить срок"),
callback_data="promo_edit_set_validity"
)
)
keyboard.row(
InlineKeyboardButton(
text=_("admin_panel_back_button", default="⬅️ Назад"),
callback_data="admin_panel_back"
)
)
prompt_text = _( prompt_text = _(
"admin_promo_edit_step4_validity", "admin_promo_edit_step4_validity",
default="✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 4 из 4:</b> Срок действия\n\nКод: <b>{code}</b>\nДни: <b>{days}</b>\nАктивации: <b>{max_act}</b>\nТекущий срок: <b>{current_validity}</b>\n\nВведите количество дней действия промокода от сегодня (1-365) или 0 для неограниченного срока:", default="✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 4 из 4:</b> Срок действия\n\nКод: <b>{code}</b>\nДни: <b>{days}</b>\nАктивации: <b>{max_act}</b>\nТекущий срок: <b>{current_validity}</b>\n\nВыберите срок действия промокода:",
code=data.get("new_code"), code=data.get("new_code"),
days=data.get("new_bonus_days"), days=data.get("new_bonus_days"),
max_act=max_activations, max_act=max_activations,
@@ -822,7 +842,7 @@ async def process_promo_edit_max_activations_handler(message: types.Message, sta
await message.answer( await message.answer(
prompt_text, prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n), reply_markup=keyboard.as_markup(),
parse_mode="HTML" parse_mode="HTML"
) )
await state.set_state(AdminStates.waiting_for_promo_edit_validity_days) await state.set_state(AdminStates.waiting_for_promo_edit_validity_days)
@@ -834,6 +854,98 @@ async def process_promo_edit_max_activations_handler(message: types.Message, sta
)) ))
# Handle edit validity choice buttons
@router.callback_query(F.data == "promo_edit_unlimited_validity", StateFilter(AdminStates.waiting_for_promo_edit_validity_days))
async def process_promo_edit_unlimited_validity(callback: types.CallbackQuery,
state: FSMContext,
i18n_data: dict,
settings: Settings,
session: AsyncSession):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await callback.answer("Language service error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
data = await state.get_data()
promo_id = data.get("edit_promo_id")
# Update promo code with unlimited validity
update_data = {
"code": data.get("new_code"),
"bonus_days": data.get("new_bonus_days"),
"max_activations": data.get("new_max_activations"),
"valid_until": None, # Unlimited
}
# Show updating message
try:
await callback.message.edit_text(
_("admin_promo_creating", default="⏳ Обновляю промокод..."),
)
except:
await callback.message.answer(
_("admin_promo_creating", default="⏳ Обновляю промокод..."),
)
await callback.answer()
updated = await promo_code_dal.update_promo_code(session, promo_id, update_data)
if updated:
await session.commit()
success_text = _(
"admin_promo_updated_success",
default="✅ Промокод обновлен!\n\n🎟 Код: <b>{code}</b>\n🎁 Бонусные дни: <b>{days}</b>\n🔢 Максимальные активации: <b>{max_act}</b>\n⏰ Действует до: <b>{validity}</b>",
code=data.get("new_code"),
days=data.get("new_bonus_days"),
max_act=data.get("new_max_activations"),
validity=_("admin_promo_valid_indefinitely", default="Неограниченно")
)
await callback.message.answer(
success_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n),
parse_mode="HTML"
)
else:
await session.rollback()
await callback.message.answer(_(
"admin_promo_update_failed",
default="❌ Ошибка обновления промокода"
))
await state.clear()
@router.callback_query(F.data == "promo_edit_set_validity", StateFilter(AdminStates.waiting_for_promo_edit_validity_days))
async def process_promo_edit_set_validity(callback: types.CallbackQuery,
state: FSMContext,
i18n_data: dict,
settings: Settings):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await callback.answer("Language service error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
prompt_text = _(
"admin_promo_edit_enter_validity_days",
default="⏰ Введите количество дней действия промокода (1-365):"
)
try:
await callback.message.edit_text(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n)
)
except Exception as e:
await callback.message.answer(
prompt_text,
reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n)
)
await callback.answer()
# Step 4: Process edited validity and finalize # Step 4: Process edited validity and finalize
@router.message(AdminStates.waiting_for_promo_edit_validity_days, F.text) @router.message(AdminStates.waiting_for_promo_edit_validity_days, F.text)
async def process_promo_edit_validity_handler(message: types.Message, state: FSMContext, async def process_promo_edit_validity_handler(message: types.Message, state: FSMContext,
@@ -851,19 +963,16 @@ async def process_promo_edit_validity_handler(message: types.Message, state: FSM
try: try:
validity_days = int(message.text.strip()) validity_days = int(message.text.strip())
if not (0 <= validity_days <= 365): if not (1 <= validity_days <= 365):
await message.answer(_( await message.answer(_(
"admin_promo_invalid_validity_days", "admin_promo_invalid_validity_days",
default="Срок действия должен быть от 0 до 365 дней (0 = неограниченно)" default="Дни действия должны быть от 1 до 365"
)) ))
return return
# Calculate validity date # Calculate validity date
valid_until_date = None valid_until_date = datetime.now(timezone.utc) + timedelta(days=validity_days)
valid_until_str_display = _("admin_promo_valid_indefinitely", default="Неограниченно") valid_until_str_display = valid_until_date.strftime('%Y-%m-%d')
if validity_days > 0:
valid_until_date = datetime.now(timezone.utc) + timedelta(days=validity_days)
valid_until_str_display = valid_until_date.strftime('%Y-%m-%d')
# Update promo code # Update promo code
update_data = { update_data = {
@@ -903,7 +1012,7 @@ async def process_promo_edit_validity_handler(message: types.Message, state: FSM
except ValueError: except ValueError:
await message.answer(_( await message.answer(_(
"admin_promo_invalid_validity_days", "admin_promo_invalid_validity_days",
default="Срок действия должен быть от 0 до 365 дней (0 = неограниченно)" default="Дни действия должны быть от 1 до 365"
)) ))
+4 -7
View File
@@ -82,14 +82,11 @@ async def create_referral_result(inline_query: InlineQuery, bot: Bot,
user_id = inline_query.from_user.id user_id = inline_query.from_user.id
referral_link = referral_service.generate_referral_link(bot_username, user_id) referral_link = referral_service.generate_referral_link(bot_username, user_id)
# Create message content # Create message content (use same text as friend message)
message_text = _( message_text = _(
"inline_referral_message", "referral_friend_message",
default="🚀 Привет! Попробуй этот крутой VPN сервис!\n\n" default="🚀 Привет! Попробуй этот VPN - быстрый, надёжный и доступный!\n\n"
"✨ Быстрый и надежный\n" "🎁 По моей ссылке тебе дадут бонусные дни к подписке!\n\n{referral_link}",
"🔒 Полная анонимность\n"
"🌍 Серверы по всему миру\n"
"💎 Бесплатный пробный период\n\n{referral_link}",
referral_link=referral_link referral_link=referral_link
) )
+6 -1
View File
@@ -83,9 +83,14 @@ async def referral_command_handler(event: Union[types.Message,
bonus_details_str = "\n".join(bonus_info_parts) if bonus_info_parts else _( bonus_details_str = "\n".join(bonus_info_parts) if bonus_info_parts else _(
"referral_no_bonuses_configured") "referral_no_bonuses_configured")
# Get referral statistics
referral_stats = await referral_service.get_referral_stats(session, inviter_user_id)
text = _("referral_program_info_new", text = _("referral_program_info_new",
referral_link=referral_link, referral_link=referral_link,
bonus_details=bonus_details_str) bonus_details=bonus_details_str,
invited_count=referral_stats["invited_count"],
purchased_count=referral_stats["purchased_count"])
from bot.keyboards.inline.user_keyboards import get_referral_link_keyboard from bot.keyboards.inline.user_keyboards import get_referral_link_keyboard
reply_markup_val = get_referral_link_keyboard(current_lang, i18n) reply_markup_val = get_referral_link_keyboard(current_lang, i18n)
+37
View File
@@ -1,5 +1,6 @@
import logging import logging
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from aiogram import Bot from aiogram import Bot
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
@@ -219,3 +220,39 @@ class ReferralService:
def generate_referral_link(self, bot_username: str, def generate_referral_link(self, bot_username: str,
inviter_user_id: int) -> str: inviter_user_id: int) -> str:
return f"https://t.me/{bot_username}?start=ref_{inviter_user_id}" return f"https://t.me/{bot_username}?start=ref_{inviter_user_id}"
async def get_referral_stats(self, session: AsyncSession, user_id: int) -> dict:
"""Get referral statistics for a user"""
from db.dal import user_dal, payment_dal
try:
# Count total invited users (referrals)
invited_count_result = await session.execute(
text("SELECT COUNT(*) FROM users WHERE referred_by_id = :user_id"),
{"user_id": user_id}
)
invited_count = invited_count_result.scalar() or 0
# Count users who made successful payments (purchased subscription)
purchased_count_result = await session.execute(
text("""
SELECT COUNT(DISTINCT u.user_id)
FROM users u
JOIN payments p ON u.user_id = p.user_id
WHERE u.referred_by_id = :user_id
AND p.status = 'succeeded'
"""),
{"user_id": user_id}
)
purchased_count = purchased_count_result.scalar() or 0
return {
"invited_count": invited_count,
"purchased_count": purchased_count
}
except Exception as e:
logging.error(f"Error getting referral stats for user {user_id}: {e}")
return {
"invited_count": 0,
"purchased_count": 0
}
+8 -5
View File
@@ -77,7 +77,7 @@
"no_button": "No", "no_button": "No",
"trial_cancelled_short": "Activation cancelled.", "trial_cancelled_short": "Activation cancelled.",
"referral_program_info_new": "Invite friends and earn bonuses!\n\nYour referral link:\n<code>{referral_link}</code>\n\n<b>Bonuses:</b>{bonus_details}", "referral_program_info_new": "🎁 <b>Referral Program</b>\n\n📊 <b>Your stats:</b>\n👥 Friends invited: <b>{invited_count}</b>\n💳 Purchased subscription: <b>{purchased_count}</b>\n\n🔗 Your link:\n<code>{referral_link}</code>\n\n💰 <b>Invitation bonuses:</b>\n{bonus_details}\n\n📢 Share the link with friends and get bonuses!",
"referral_bonus_per_period": "\n\n🎁 For a friend's {months}-month subscription:\n ➢ You: <b>{inviter_bonus_days} days</b>\n ➢ Friend: <b>{referee_bonus_days} days</b>", "referral_bonus_per_period": "\n\n🎁 For a friend's {months}-month subscription:\n ➢ You: <b>{inviter_bonus_days} days</b>\n ➢ Friend: <b>{referee_bonus_days} days</b>",
"no_bonus_days": "0", "no_bonus_days": "0",
"referral_no_bonuses_configured": "\nNo referral bonuses configured.", "referral_no_bonuses_configured": "\nNo referral bonuses configured.",
@@ -271,7 +271,7 @@
"admin_user_logs_error": "❌ Error loading user actions", "admin_user_logs_error": "❌ Error loading user actions",
"admin_direct_message_signature": "\n\n---\n💬 Message from administrator", "admin_direct_message_signature": "\n\n---\n💬 Message from administrator",
"inline_referral_message": "🚀 Hey! Try this awesome VPN service!\n\n✨ Fast and reliable\n🔒 Complete anonymity\n🌍 Servers worldwide\n💎 Free trial period\n\nCheck it out: {referral_link}",
"inline_referral_title": "🎁 Invite a Friend", "inline_referral_title": "🎁 Invite a Friend",
"inline_referral_description": "Share referral link to get bonuses", "inline_referral_description": "Share referral link to get bonuses",
@@ -311,10 +311,13 @@
"admin_promo_edit_step1_code": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 1 of 4:</b> Promo Code\n\nCurrent code: <b>{current_code}</b>\n\nEnter new promo code (3-30 characters, letters and numbers only) or send current one to keep:", "admin_promo_edit_step1_code": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 1 of 4:</b> Promo Code\n\nCurrent code: <b>{current_code}</b>\n\nEnter new promo code (3-30 characters, letters and numbers only) or send current one to keep:",
"admin_promo_edit_step2_bonus_days": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 2 of 4:</b> Bonus Days\n\nCode: <b>{code}</b>\nCurrent days: <b>{current_days}</b>\n\nEnter number of bonus days (1-365):", "admin_promo_edit_step2_bonus_days": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 2 of 4:</b> Bonus Days\n\nCode: <b>{code}</b>\nCurrent days: <b>{current_days}</b>\n\nEnter number of bonus days (1-365):",
"admin_promo_edit_step3_max_activations": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 3 of 4:</b> Max Activations\n\nCode: <b>{code}</b>\nDays: <b>{days}</b>\nCurrent activations: <b>{current_max}</b>\n\nEnter maximum number of activations (1-10000):", "admin_promo_edit_step3_max_activations": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 3 of 4:</b> Max Activations\n\nCode: <b>{code}</b>\nDays: <b>{days}</b>\nCurrent activations: <b>{current_max}</b>\n\nEnter maximum number of activations (1-10000):",
"admin_promo_edit_step4_validity": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 4 of 4:</b> Validity Period\n\nCode: <b>{code}</b>\nDays: <b>{days}</b>\nActivations: <b>{max_act}</b>\nCurrent validity: <b>{current_validity}</b>\n\nEnter number of days the promo code is valid from today (1-365) or 0 for unlimited:", "admin_promo_edit_step4_validity": "✏️ <b>Editing Promo Code</b>\n\n<b>Step 4 of 4:</b> Validity Period\n\nCode: <b>{code}</b>\nDays: <b>{days}</b>\nActivations: <b>{max_act}</b>\nCurrent validity: <b>{current_validity}</b>\n\nChoose the validity period for the promo code:",
"admin_promo_edit_unlimited_validity": "♾️ Unlimited",
"admin_promo_edit_set_validity": "⏰ Set Period",
"admin_promo_edit_enter_validity_days": "⏰ Enter the number of validity days for the promo code (1-365):",
"admin_promo_updated_success": "✅ Promo code updated!\n\n🎟 Code: <b>{code}</b>\n🎁 Bonus days: <b>{days}</b>\n🔢 Max activations: <b>{max_act}</b>\n⏰ Valid until: <b>{validity}</b>", "admin_promo_updated_success": "✅ Promo code updated!\n\n🎟 Code: <b>{code}</b>\n🎁 Bonus days: <b>{days}</b>\n🔢 Max activations: <b>{max_act}</b>\n⏰ Valid until: <b>{validity}</b>",
"admin_promo_update_failed": "❌ Error updating promo code", "admin_promo_update_failed": "❌ Error updating promo code",
"admin_promo_invalid_validity_days": "❌ Validity days must be from 0 to 365 days (0 = unlimited)", "admin_promo_invalid_validity_days": "❌ Validity days must be from 1 to 365",
"admin_logs_export_csv": "📄 Export to CSV", "admin_logs_export_csv": "📄 Export to CSV",
"admin_logs_csv_export_started": "📄 Starting log export to CSV...", "admin_logs_csv_export_started": "📄 Starting log export to CSV...",
@@ -452,7 +455,7 @@
"admin_panel_stats_fetch_error": "Error fetching panel data", "admin_panel_stats_fetch_error": "Error fetching panel data",
"admin_panel_stats_error_details": "Details", "admin_panel_stats_error_details": "Details",
"inline_referral_message": "🎁 Get bonuses for friends!\n\nConnect friends to fast VPN and get bonus days for each new user.\n\n💰 Your referral link:\n{referral_link}", "inline_referral_message": "🚀 Hey! Try this VPN - it's fast, reliable and affordable!\n\n🎁 Use my link to get bonus days with your subscription!\n\n{referral_link}",
"inline_referral_title": "Referral link", "inline_referral_title": "Referral link",
"inline_referral_description": "Share referral link to get bonuses", "inline_referral_description": "Share referral link to get bonuses",
"inline_stats_title": "Bot statistics", "inline_stats_title": "Bot statistics",
+8 -5
View File
@@ -77,7 +77,7 @@
"no_button": "Нет", "no_button": "Нет",
"trial_cancelled_short": "Активация отменена.", "trial_cancelled_short": "Активация отменена.",
"referral_program_info_new": "Приглашайте друзей и получайте бонусы!\n\nВаша реф. ссылка:\n<code>{referral_link}</code>\n\n<b>Бонусы:</b>{bonus_details}", "referral_program_info_new": "🎁 <b>Реферальная программа</b>\n\n📊 <b>Твоя статистика:</b>\n👥 Приглашено друзей: <b>{invited_count}</b>\n💳 Купили подписку: <b>{purchased_count}</b>\n\n🔗 Твоя ссылка:\n<code>{referral_link}</code>\n\n💰 <b>Бонусы за приглашения:</b>\n{bonus_details}\n\n📢 Поделись ссылкой с друзьями и получай бонусы!",
"referral_bonus_per_period": "\n\n🎁 За {months}-мес. подписку друга:\n ➢ Вы: <b>{inviter_bonus_days} дн.</b>\n ➢ Друг: <b>{referee_bonus_days} дн.</b>", "referral_bonus_per_period": "\n\n🎁 За {months}-мес. подписку друга:\n ➢ Вы: <b>{inviter_bonus_days} дн.</b>\n ➢ Друг: <b>{referee_bonus_days} дн.</b>",
"no_bonus_days": "0", "no_bonus_days": "0",
"referral_no_bonuses_configured": "\nРеферальные бонусы не настроены.", "referral_no_bonuses_configured": "\nРеферальные бонусы не настроены.",
@@ -271,7 +271,7 @@
"admin_user_logs_error": "❌ Ошибка загрузки действий пользователя", "admin_user_logs_error": "❌ Ошибка загрузки действий пользователя",
"admin_direct_message_signature": "\n\n---\n💬 Сообщение от администратора", "admin_direct_message_signature": "\n\n---\n💬 Сообщение от администратора",
"inline_referral_message": "🚀 Привет! Попробуй этот крутой VPN сервис!\n\n✨ Быстрый и надежный\n🔒 Полная анонимность\n🌍 Серверы по всему миру\n💎 Бесплатный пробный период\n\nПереходи по ссылке: {referral_link}",
"inline_referral_title": "🎁 Пригласить друга", "inline_referral_title": "🎁 Пригласить друга",
"inline_referral_description": "Поделиться реферальной ссылкой для получения бонусов", "inline_referral_description": "Поделиться реферальной ссылкой для получения бонусов",
@@ -312,10 +312,13 @@
"admin_promo_edit_step1_code": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 1 из 4:</b> Код промокода\n\nТекущий код: <b>{current_code}</b>\n\nВведите новый код промокода (3-30 символов, только буквы и цифры) или отправьте текущий для сохранения:", "admin_promo_edit_step1_code": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 1 из 4:</b> Код промокода\n\nТекущий код: <b>{current_code}</b>\n\nВведите новый код промокода (3-30 символов, только буквы и цифры) или отправьте текущий для сохранения:",
"admin_promo_edit_step2_bonus_days": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 2 из 4:</b> Бонусные дни\n\nКод: <b>{code}</b>\nТекущие дни: <b>{current_days}</b>\n\nВведите количество бонусных дней (1-365):", "admin_promo_edit_step2_bonus_days": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 2 из 4:</b> Бонусные дни\n\nКод: <b>{code}</b>\nТекущие дни: <b>{current_days}</b>\n\nВведите количество бонусных дней (1-365):",
"admin_promo_edit_step3_max_activations": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 3 из 4:</b> Максимальные активации\n\nКод: <b>{code}</b>\nДни: <b>{days}</b>\nТекущие активации: <b>{current_max}</b>\n\nВведите максимальное количество активаций (1-10000):", "admin_promo_edit_step3_max_activations": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 3 из 4:</b> Максимальные активации\n\nКод: <b>{code}</b>\nДни: <b>{days}</b>\nТекущие активации: <b>{current_max}</b>\n\nВведите максимальное количество активаций (1-10000):",
"admin_promo_edit_step4_validity": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 4 из 4:</b> Срок действия\n\nКод: <b>{code}</b>\nДни: <b>{days}</b>\nАктивации: <b>{max_act}</b>\nТекущий срок: <b>{current_validity}</b>\n\nВведите количество дней действия промокода от сегодня (1-365) или 0 для неограниченного срока:", "admin_promo_edit_step4_validity": "✏️ <b>Редактирование промокода</b>\n\n<b>Шаг 4 из 4:</b> Срок действия\n\nКод: <b>{code}</b>\nДни: <b>{days}</b>\nАктивации: <b>{max_act}</b>\nТекущий срок: <b>{current_validity}</b>\n\nВыберите срок действия промокода:",
"admin_promo_edit_unlimited_validity": "♾️ Неограниченно",
"admin_promo_edit_set_validity": "⏰ Установить срок",
"admin_promo_edit_enter_validity_days": "⏰ Введите количество дней действия промокода (1-365):",
"admin_promo_updated_success": "✅ Промокод обновлен!\n\n🎟 Код: <b>{code}</b>\n🎁 Бонусные дни: <b>{days}</b>\n🔢 Максимальные активации: <b>{max_act}</b>\n⏰ Действует до: <b>{validity}</b>", "admin_promo_updated_success": "✅ Промокод обновлен!\n\n🎟 Код: <b>{code}</b>\n🎁 Бонусные дни: <b>{days}</b>\n🔢 Максимальные активации: <b>{max_act}</b>\n⏰ Действует до: <b>{validity}</b>",
"admin_promo_update_failed": "❌ Ошибка обновления промокода", "admin_promo_update_failed": "❌ Ошибка обновления промокода",
"admin_promo_invalid_validity_days": "❌ Срок действия должен быть от 0 до 365 дней (0 = неограниченно)", "admin_promo_invalid_validity_days": "❌ Дни действия должны быть от 1 до 365",
"admin_logs_export_csv": "📄 Экспорт в CSV", "admin_logs_export_csv": "📄 Экспорт в CSV",
"admin_logs_csv_export_started": "📄 Начинаю экспорт логов в CSV...", "admin_logs_csv_export_started": "📄 Начинаю экспорт логов в CSV...",
@@ -453,7 +456,7 @@
"admin_panel_stats_fetch_error": "Ошибка получения данных с панели", "admin_panel_stats_fetch_error": "Ошибка получения данных с панели",
"admin_panel_stats_error_details": "Детали", "admin_panel_stats_error_details": "Детали",
"inline_referral_message": "🎁 Получи бонусы за друзей!\n\nПодключай друзей к быстрому VPN и получай бонусные дни за каждого нового пользователя.\n\n💰 Твоя реферальная ссылка:\n{referral_link}", "inline_referral_message": "🚀 Привет! Попробуй этот VPN - быстрый, надёжный и доступный!\n\n🎁 По моей ссылке тебе дадут бонусные дни к подписке!\n\n{referral_link}",
"inline_referral_title": "Реферальная ссылка", "inline_referral_title": "Реферальная ссылка",
"inline_referral_description": "Поделиться реферальной ссылкой для получения бонусов", "inline_referral_description": "Поделиться реферальной ссылкой для получения бонусов",
"inline_stats_title": "Статистика бота", "inline_stats_title": "Статистика бота",