mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-03-02 00:03:05 +00:00
Revert "Add admin controls for traffic package visibility"
This commit is contained in:
@@ -21,25 +21,6 @@ logger = logging.getLogger(__name__)
|
||||
PriceItem = Tuple[str, str, int]
|
||||
|
||||
|
||||
TRAFFIC_PACKAGE_FIELDS: Tuple[Tuple[int, str], ...] = (
|
||||
(5, "PRICE_TRAFFIC_5GB"),
|
||||
(10, "PRICE_TRAFFIC_10GB"),
|
||||
(25, "PRICE_TRAFFIC_25GB"),
|
||||
(50, "PRICE_TRAFFIC_50GB"),
|
||||
(100, "PRICE_TRAFFIC_100GB"),
|
||||
(250, "PRICE_TRAFFIC_250GB"),
|
||||
(500, "PRICE_TRAFFIC_500GB"),
|
||||
(1000, "PRICE_TRAFFIC_1000GB"),
|
||||
(0, "PRICE_TRAFFIC_UNLIMITED"),
|
||||
)
|
||||
|
||||
TRAFFIC_PACKAGE_FIELD_MAP: Dict[int, str] = {gb: field for gb, field in TRAFFIC_PACKAGE_FIELDS}
|
||||
TRAFFIC_PACKAGE_ORDER: Tuple[int, ...] = tuple(gb for gb, _ in TRAFFIC_PACKAGE_FIELDS)
|
||||
TRAFFIC_PACKAGE_ORDER_INDEX: Dict[int, int] = {
|
||||
gb: index for index, gb in enumerate(TRAFFIC_PACKAGE_ORDER)
|
||||
}
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class ChoiceOption:
|
||||
value: Any
|
||||
@@ -197,71 +178,6 @@ SETTING_ENTRY_BY_KEY: Dict[str, SettingEntry] = {
|
||||
}
|
||||
|
||||
|
||||
def _traffic_package_sort_key(package: Dict[str, Any]) -> Tuple[int, int]:
|
||||
order_index = TRAFFIC_PACKAGE_ORDER_INDEX.get(package["gb"])
|
||||
if order_index is not None:
|
||||
return (0, order_index)
|
||||
return (1, package["gb"])
|
||||
|
||||
|
||||
def _collect_traffic_packages() -> List[Dict[str, Any]]:
|
||||
raw_packages = settings.get_traffic_packages()
|
||||
|
||||
packages_map: Dict[int, Dict[str, Any]] = {}
|
||||
for package in raw_packages:
|
||||
gb = int(package.get("gb", 0))
|
||||
packages_map[gb] = {
|
||||
"gb": gb,
|
||||
"price": int(package.get("price") or 0),
|
||||
"enabled": bool(package.get("enabled", True)),
|
||||
"field": TRAFFIC_PACKAGE_FIELD_MAP.get(gb),
|
||||
}
|
||||
|
||||
for gb, field in TRAFFIC_PACKAGE_FIELDS:
|
||||
if not hasattr(settings, field):
|
||||
continue
|
||||
|
||||
price = getattr(settings, field)
|
||||
existing = packages_map.get(gb)
|
||||
enabled = existing["enabled"] if existing is not None else True
|
||||
|
||||
packages_map[gb] = {
|
||||
"gb": gb,
|
||||
"price": int(price),
|
||||
"enabled": enabled,
|
||||
"field": field,
|
||||
}
|
||||
|
||||
packages = list(packages_map.values())
|
||||
packages.sort(key=_traffic_package_sort_key)
|
||||
return packages
|
||||
|
||||
|
||||
def _serialize_traffic_packages(packages: Iterable[Dict[str, Any]]) -> str:
|
||||
parts = []
|
||||
for package in packages:
|
||||
enabled_flag = "true" if package.get("enabled") else "false"
|
||||
parts.append(f"{int(package['gb'])}:{int(package['price'])}:{enabled_flag}")
|
||||
return ",".join(parts)
|
||||
|
||||
|
||||
async def _save_traffic_packages(
|
||||
db: AsyncSession,
|
||||
packages: Iterable[Dict[str, Any]],
|
||||
*,
|
||||
skip_if_same: bool = False,
|
||||
) -> bool:
|
||||
new_value = _serialize_traffic_packages(packages)
|
||||
current_value = bot_configuration_service.get_current_value("TRAFFIC_PACKAGES_CONFIG") or ""
|
||||
|
||||
if skip_if_same and current_value == new_value:
|
||||
return False
|
||||
|
||||
await bot_configuration_service.set_value(db, "TRAFFIC_PACKAGES_CONFIG", new_value)
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
|
||||
def _language_code(language: str | None) -> str:
|
||||
return (language or "ru").split("-")[0].lower()
|
||||
|
||||
@@ -320,17 +236,23 @@ def _get_period_items(lang_code: str) -> List[PriceItem]:
|
||||
|
||||
|
||||
def _get_traffic_items(lang_code: str) -> List[PriceItem]:
|
||||
packages = _collect_traffic_packages()
|
||||
traffic_keys: Tuple[Tuple[int, str], ...] = (
|
||||
(5, "PRICE_TRAFFIC_5GB"),
|
||||
(10, "PRICE_TRAFFIC_10GB"),
|
||||
(25, "PRICE_TRAFFIC_25GB"),
|
||||
(50, "PRICE_TRAFFIC_50GB"),
|
||||
(100, "PRICE_TRAFFIC_100GB"),
|
||||
(250, "PRICE_TRAFFIC_250GB"),
|
||||
(500, "PRICE_TRAFFIC_500GB"),
|
||||
(1000, "PRICE_TRAFFIC_1000GB"),
|
||||
(0, "PRICE_TRAFFIC_UNLIMITED"),
|
||||
)
|
||||
|
||||
items: List[PriceItem] = []
|
||||
for package in packages:
|
||||
field = package.get("field")
|
||||
if not field:
|
||||
continue
|
||||
|
||||
label = _format_traffic_label(package["gb"], lang_code)
|
||||
icon = "✅" if package["enabled"] else "⚪️"
|
||||
items.append((field, f"{icon} {label}", int(package["price"])))
|
||||
for gb, key in traffic_keys:
|
||||
if hasattr(settings, key):
|
||||
price = getattr(settings, key)
|
||||
items.append((key, _format_traffic_label(gb, lang_code), price))
|
||||
return items
|
||||
|
||||
|
||||
@@ -363,17 +285,17 @@ def _build_period_summary(items: Iterable[PriceItem], lang_code: str, fallback:
|
||||
return ", ".join(parts) if parts else fallback
|
||||
|
||||
|
||||
def _build_traffic_summary(lang_code: str, fallback: str) -> str:
|
||||
packages = _collect_traffic_packages()
|
||||
enabled_packages = [package for package in packages if package["enabled"]]
|
||||
|
||||
if not enabled_packages:
|
||||
return fallback
|
||||
|
||||
def _build_traffic_summary(items: Iterable[PriceItem], lang_code: str, fallback: str) -> str:
|
||||
parts: List[str] = []
|
||||
for package in enabled_packages:
|
||||
short_label = _format_traffic_label(package["gb"], lang_code, short=True)
|
||||
parts.append(f"{short_label}: {settings.format_price(int(package['price']))}")
|
||||
for key, label, price in items:
|
||||
if key.endswith("UNLIMITED"):
|
||||
short_label = "∞"
|
||||
else:
|
||||
digits = ''.join(ch for ch in key if ch.isdigit())
|
||||
unit = "ГБ" if lang_code == "ru" else "GB"
|
||||
short_label = f"{digits}{unit}" if digits else label
|
||||
|
||||
parts.append(f"{short_label}: {settings.format_price(price)}")
|
||||
|
||||
return ", ".join(parts) if parts else fallback
|
||||
|
||||
@@ -485,68 +407,6 @@ def _build_settings_section(
|
||||
return "\n".join(lines).strip(), keyboard
|
||||
|
||||
|
||||
def _build_traffic_options_section(language: str) -> Tuple[str, types.InlineKeyboardMarkup]:
|
||||
texts = get_texts(language)
|
||||
lang_code = _language_code(language)
|
||||
packages = _collect_traffic_packages()
|
||||
|
||||
title = texts.t(
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_TITLE",
|
||||
"🚦 Отображение пакетов трафика",
|
||||
)
|
||||
|
||||
lines: List[str] = [title, ""]
|
||||
|
||||
enabled_labels = [
|
||||
_format_traffic_label(package["gb"], lang_code, short=True)
|
||||
for package in packages
|
||||
if package["enabled"]
|
||||
]
|
||||
|
||||
if enabled_labels:
|
||||
lines.append(
|
||||
texts.t(
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_ACTIVE",
|
||||
"Активные пакеты: {items}",
|
||||
).format(items=", ".join(enabled_labels))
|
||||
)
|
||||
else:
|
||||
lines.append(
|
||||
texts.t(
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_NONE",
|
||||
"Активных пакетов нет.",
|
||||
)
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append(
|
||||
texts.t(
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_PROMPT",
|
||||
"Нажмите на пакет, чтобы включить или выключить его отображение.",
|
||||
)
|
||||
)
|
||||
|
||||
keyboard_rows: List[List[types.InlineKeyboardButton]] = []
|
||||
buttons: List[types.InlineKeyboardButton] = []
|
||||
|
||||
for package in packages:
|
||||
icon = "✅" if package["enabled"] else "⚪️"
|
||||
label = _format_traffic_label(package["gb"], lang_code, short=True)
|
||||
buttons.append(
|
||||
types.InlineKeyboardButton(
|
||||
text=f"{icon} {label}",
|
||||
callback_data=f"admin_pricing_toggle_traffic:{package['gb']}",
|
||||
)
|
||||
)
|
||||
|
||||
for i in range(0, len(buttons), 3):
|
||||
keyboard_rows.append(buttons[i : i + 3])
|
||||
|
||||
keyboard_rows.append([types.InlineKeyboardButton(text=texts.BACK, callback_data="admin_pricing")])
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=keyboard_rows)
|
||||
return "\n".join(lines), keyboard
|
||||
|
||||
|
||||
def _build_period_options_section(language: str) -> Tuple[str, types.InlineKeyboardMarkup]:
|
||||
texts = get_texts(language)
|
||||
lang_code = _language_code(language)
|
||||
@@ -625,7 +485,7 @@ def _build_overview(language: str) -> Tuple[str, types.InlineKeyboardMarkup]:
|
||||
|
||||
fallback = texts.t("ADMIN_PRICING_SUMMARY_EMPTY", "—")
|
||||
summary_periods = _build_period_summary(period_items, lang_code, fallback)
|
||||
summary_traffic = _build_traffic_summary(lang_code, fallback)
|
||||
summary_traffic = _build_traffic_summary(traffic_items, lang_code, fallback)
|
||||
summary_extra = _build_extra_summary(extra_items, fallback)
|
||||
summary_trial = _format_trial_summary(lang_code)
|
||||
summary_core = _format_core_summary(lang_code)
|
||||
@@ -676,13 +536,6 @@ def _build_overview(language: str) -> Tuple[str, types.InlineKeyboardMarkup]:
|
||||
text=texts.t("ADMIN_PRICING_BUTTON_TRAFFIC", "📦 Пакеты трафика"),
|
||||
callback_data="admin_pricing_section:traffic",
|
||||
),
|
||||
types.InlineKeyboardButton(
|
||||
text=texts.t(
|
||||
"ADMIN_PRICING_BUTTON_TRAFFIC_OPTIONS",
|
||||
"🚦 Отображение пакетов",
|
||||
),
|
||||
callback_data="admin_pricing_section:traffic_options",
|
||||
),
|
||||
types.InlineKeyboardButton(
|
||||
text=texts.t("ADMIN_PRICING_BUTTON_EXTRA", "➕ Дополнительно"),
|
||||
callback_data="admin_pricing_section:extra",
|
||||
@@ -711,8 +564,6 @@ def _build_section(
|
||||
elif section == "extra":
|
||||
items = _get_extra_items(lang_code)
|
||||
title = texts.t("ADMIN_PRICING_SECTION_EXTRA_TITLE", "➕ Дополнительные опции")
|
||||
elif section == "traffic_options":
|
||||
return _build_traffic_options_section(language)
|
||||
elif section in SETTING_ENTRIES_BY_SECTION:
|
||||
return _build_settings_section(section, language)
|
||||
elif section == "period_options":
|
||||
@@ -1071,10 +922,6 @@ async def process_pricing_input(
|
||||
await bot_configuration_service.set_value(db, key, new_value)
|
||||
await db.commit()
|
||||
|
||||
if key.startswith("PRICE_TRAFFIC_"):
|
||||
packages = _collect_traffic_packages()
|
||||
await _save_traffic_packages(db, packages, skip_if_same=True)
|
||||
|
||||
if mode == "price":
|
||||
label = _resolve_label(section, key, db_user.language)
|
||||
await message.answer(
|
||||
@@ -1196,57 +1043,6 @@ async def select_setting_choice(
|
||||
await _render_message(callback.message, text, keyboard)
|
||||
|
||||
|
||||
@admin_required
|
||||
@error_handler
|
||||
async def toggle_traffic_package(
|
||||
callback: types.CallbackQuery,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
state: FSMContext,
|
||||
) -> None:
|
||||
try:
|
||||
_, gb_raw = callback.data.split(":", 1)
|
||||
gb_value = int(gb_raw)
|
||||
except (ValueError, TypeError):
|
||||
await callback.answer()
|
||||
return
|
||||
|
||||
texts = get_texts(db_user.language)
|
||||
packages = _collect_traffic_packages()
|
||||
|
||||
target_index = next((index for index, pkg in enumerate(packages) if pkg["gb"] == gb_value), None)
|
||||
if target_index is None:
|
||||
await callback.answer()
|
||||
return
|
||||
|
||||
enabled_count = sum(1 for pkg in packages if pkg["enabled"])
|
||||
target_package = packages[target_index]
|
||||
|
||||
if target_package["enabled"] and enabled_count <= 1:
|
||||
await callback.answer(
|
||||
texts.t(
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_MIN",
|
||||
"Должен оставаться хотя бы один пакет.",
|
||||
),
|
||||
show_alert=True,
|
||||
)
|
||||
return
|
||||
|
||||
target_package["enabled"] = not target_package["enabled"]
|
||||
|
||||
await _save_traffic_packages(db, packages)
|
||||
|
||||
status_text = (
|
||||
texts.t("ADMIN_PRICING_TRAFFIC_PACKAGE_ENABLED", "Пакет включен.")
|
||||
if target_package["enabled"]
|
||||
else texts.t("ADMIN_PRICING_TRAFFIC_PACKAGE_DISABLED", "Пакет отключен.")
|
||||
)
|
||||
await callback.answer(status_text)
|
||||
|
||||
text, keyboard = _build_traffic_options_section(db_user.language)
|
||||
await _render_message(callback.message, text, keyboard)
|
||||
|
||||
|
||||
@admin_required
|
||||
@error_handler
|
||||
async def toggle_period_option(
|
||||
@@ -1331,10 +1127,6 @@ def register_handlers(dp: Dispatcher) -> None:
|
||||
select_setting_choice,
|
||||
F.data.startswith("admin_pricing_choice:"),
|
||||
)
|
||||
dp.callback_query.register(
|
||||
toggle_traffic_package,
|
||||
F.data.startswith("admin_pricing_toggle_traffic:"),
|
||||
)
|
||||
dp.callback_query.register(
|
||||
toggle_period_option,
|
||||
F.data.startswith("admin_pricing_toggle_period:"),
|
||||
|
||||
@@ -769,13 +769,5 @@
|
||||
"ADMIN_PUBLIC_OFFER_ENABLED": "✅ Offer enabled",
|
||||
"ADMIN_PUBLIC_OFFER_DISABLED": "🚫 Offer disabled",
|
||||
"ADMIN_PUBLIC_OFFER_RETURN_TO_EDIT": "⬅️ Back to editing",
|
||||
"ADMIN_PUBLIC_OFFER_EDIT_CANCELLED": "Offer editing cancelled.",
|
||||
"ADMIN_PRICING_BUTTON_TRAFFIC_OPTIONS": "🚦 Package visibility",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_ENABLED": "Package enabled.",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_DISABLED": "Package disabled.",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_MIN": "At least one package must remain.",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_TITLE": "🚦 Traffic package visibility",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_ACTIVE": "Active packages: {items}",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_NONE": "No active packages.",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_PROMPT": "Tap a package to toggle its visibility."
|
||||
"ADMIN_PUBLIC_OFFER_EDIT_CANCELLED": "Offer editing cancelled."
|
||||
}
|
||||
|
||||
@@ -803,13 +803,5 @@
|
||||
"ADMIN_SUPPORT_EDIT_DESCRIPTION_CONTACT_HINT": "Добавьте в описание при необходимости.",
|
||||
"ADMIN_SUPPORT_DESCRIPTION_UPDATED": "✅ Описание обновлено.",
|
||||
"ADMIN_SUPPORT_DESCRIPTION_SENT": "Текст отправлен ниже",
|
||||
"ADMIN_SUPPORT_MESSAGE_DELETED": "Сообщение удалено",
|
||||
"ADMIN_PRICING_BUTTON_TRAFFIC_OPTIONS": "🚦 Отображение пакетов",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_ENABLED": "Пакет включен.",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_DISABLED": "Пакет отключен.",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_MIN": "Должен оставаться хотя бы один пакет.",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_TITLE": "🚦 Отображение пакетов трафика",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_ACTIVE": "Активные пакеты: {items}",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_NONE": "Активных пакетов нет.",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_PROMPT": "Нажмите на пакет, чтобы включить или выключить его отображение."
|
||||
"ADMIN_SUPPORT_MESSAGE_DELETED": "Сообщение удалено"
|
||||
}
|
||||
|
||||
@@ -1032,7 +1032,6 @@
|
||||
"ADMIN_PRICING_BUTTON_PERIOD_OPTIONS": "🗓 Available periods",
|
||||
"ADMIN_PRICING_BUTTON_PERIODS": "🗓 Subscription periods",
|
||||
"ADMIN_PRICING_BUTTON_TRAFFIC": "📦 Traffic packages",
|
||||
"ADMIN_PRICING_BUTTON_TRAFFIC_OPTIONS": "🚦 Package visibility",
|
||||
"ADMIN_PRICING_BUTTON_TRIAL": "🎁 Trial period",
|
||||
"ADMIN_PRICING_CHOICE_ALREADY": "This option is already active.",
|
||||
"ADMIN_PRICING_CHOICE_UPDATED": "Selected: {label}",
|
||||
@@ -1049,9 +1048,6 @@
|
||||
"ADMIN_PRICING_PERIOD_DISABLED": "Period disabled.",
|
||||
"ADMIN_PRICING_PERIOD_ENABLED": "Period enabled.",
|
||||
"ADMIN_PRICING_PERIOD_MIN": "At least one period must remain.",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_ENABLED": "Package enabled.",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_DISABLED": "Package disabled.",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_MIN": "At least one package must remain.",
|
||||
"ADMIN_PRICING_SECTION_CORE_TITLE": "⚙️ Core limits",
|
||||
"ADMIN_PRICING_SECTION_CURRENT": "Current values:",
|
||||
"ADMIN_PRICING_SECTION_EMPTY": "No values available.",
|
||||
@@ -1064,10 +1060,6 @@
|
||||
"ADMIN_PRICING_SECTION_PROMPT": "Select what to update:",
|
||||
"ADMIN_PRICING_SECTION_SETTINGS_GENERIC": "⚙️ Settings",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_TITLE": "📦 Traffic packages",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_TITLE": "🚦 Traffic package visibility",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_ACTIVE": "Active packages: {items}",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_NONE": "No active packages.",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_PROMPT": "Tap a package to toggle its visibility.",
|
||||
"ADMIN_PRICING_SECTION_TRIAL_TITLE": "🎁 Trial period",
|
||||
"ADMIN_PRICING_SETTING_CANCEL_HINT": "Reply \"Cancel\" to go back without changes.",
|
||||
"ADMIN_PRICING_SETTING_CURRENT": "Current value",
|
||||
|
||||
@@ -1034,7 +1034,6 @@
|
||||
"ADMIN_PRICING_BUTTON_PERIOD_OPTIONS": "🗓 Доступные периоды",
|
||||
"ADMIN_PRICING_BUTTON_PERIODS": "🗓 Периоды подписки",
|
||||
"ADMIN_PRICING_BUTTON_TRAFFIC": "📦 Пакеты трафика",
|
||||
"ADMIN_PRICING_BUTTON_TRAFFIC_OPTIONS": "🚦 Отображение пакетов",
|
||||
"ADMIN_PRICING_BUTTON_TRIAL": "🎁 Пробный период",
|
||||
"ADMIN_PRICING_CHOICE_ALREADY": "Это значение уже активно.",
|
||||
"ADMIN_PRICING_CHOICE_UPDATED": "Выбрано: {label}",
|
||||
@@ -1051,9 +1050,6 @@
|
||||
"ADMIN_PRICING_PERIOD_DISABLED": "Период отключен.",
|
||||
"ADMIN_PRICING_PERIOD_ENABLED": "Период включен.",
|
||||
"ADMIN_PRICING_PERIOD_MIN": "Должен оставаться хотя бы один период.",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_ENABLED": "Пакет включен.",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_DISABLED": "Пакет отключен.",
|
||||
"ADMIN_PRICING_TRAFFIC_PACKAGE_MIN": "Должен оставаться хотя бы один пакет.",
|
||||
"ADMIN_PRICING_SECTION_CORE_TITLE": "⚙️ Базовые лимиты",
|
||||
"ADMIN_PRICING_SECTION_CURRENT": "Текущие значения:",
|
||||
"ADMIN_PRICING_SECTION_EMPTY": "Нет доступных значений.",
|
||||
@@ -1066,10 +1062,6 @@
|
||||
"ADMIN_PRICING_SECTION_PROMPT": "Выберите что изменить:",
|
||||
"ADMIN_PRICING_SECTION_SETTINGS_GENERIC": "⚙️ Настройки",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_TITLE": "📦 Пакеты трафика",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_TITLE": "🚦 Отображение пакетов трафика",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_ACTIVE": "Активные пакеты: {items}",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_NONE": "Активных пакетов нет.",
|
||||
"ADMIN_PRICING_SECTION_TRAFFIC_OPTIONS_PROMPT": "Нажмите на пакет, чтобы включить или выключить его отображение.",
|
||||
"ADMIN_PRICING_SECTION_TRIAL_TITLE": "🎁 Пробный период",
|
||||
"ADMIN_PRICING_SETTING_CANCEL_HINT": "Чтобы вернуться без изменений, ответьте «Отмена».",
|
||||
"ADMIN_PRICING_SETTING_CURRENT": "Текущее значение",
|
||||
|
||||
Reference in New Issue
Block a user