feat: streamline admin tariff settings
This commit is contained in:
@@ -7,7 +7,6 @@
|
||||
EyeOff,
|
||||
FileText,
|
||||
Search,
|
||||
TriangleAlert,
|
||||
X,
|
||||
} from "$components/ui/icons.js";
|
||||
import * as UiIcons from "$components/ui/icons.js";
|
||||
@@ -28,7 +27,12 @@
|
||||
const settingsStore = getContext("settingsStore");
|
||||
|
||||
$: ({ settingsSections, settingsLoading, settingsDirty, settingsSaving } = $settingsStore);
|
||||
$: visibleSettingsSections = settingsSections.filter((section) => section.id !== "appearance");
|
||||
|
||||
const SETTINGS_SECTION_IDS_HIDDEN_IN_GENERAL_SETTINGS = new Set(["appearance", "pricing"]);
|
||||
|
||||
$: visibleSettingsSections = settingsSections.filter(
|
||||
(section) => !SETTINGS_SECTION_IDS_HIDDEN_IN_GENERAL_SETTINGS.has(section.id)
|
||||
);
|
||||
|
||||
let settingsOpenSections = [];
|
||||
let settingsOpenSubsections = {};
|
||||
@@ -41,22 +45,11 @@
|
||||
const PLATEGA_SBP_KEYS = new Set(["PLATEGA_SBP_ENABLED", "PLATEGA_SBP_METHOD"]);
|
||||
const PLATEGA_CRYPTO_KEYS = new Set(["PLATEGA_CRYPTO_ENABLED", "PLATEGA_CRYPTO_METHOD"]);
|
||||
const PLATEGA_LEGACY_KEYS = new Set(["PLATEGA_PAYMENT_METHOD"]);
|
||||
const LEGACY_TARIFF_TRAFFIC_KEYS = new Set(["TRAFFIC_PACKAGES", "STARS_TRAFFIC_PACKAGES"]);
|
||||
const TRIAL_TARIFF_KEYS = new Set([
|
||||
"TRIAL_ENABLED",
|
||||
"TRIAL_DURATION_DAYS",
|
||||
"TRIAL_TRAFFIC_LIMIT_GB",
|
||||
"TRIAL_TRAFFIC_STRATEGY",
|
||||
"TRIAL_SQUAD_UUIDS",
|
||||
]);
|
||||
const SEMANTIC_FIELD_GROUP_ORDER = {
|
||||
platega_common: 1,
|
||||
platega_sbp: 2,
|
||||
platega_crypto: 3,
|
||||
platega_legacy: 4,
|
||||
trial_tariff_settings: 0,
|
||||
legacy_tariff_periods: 1,
|
||||
legacy_tariff_traffic: 2,
|
||||
};
|
||||
|
||||
$: settingsAllOpen =
|
||||
@@ -289,42 +282,10 @@
|
||||
);
|
||||
}
|
||||
|
||||
function legacyTariffSemanticGroup(field) {
|
||||
const key = String(field?.key || "");
|
||||
if (TRIAL_TARIFF_KEYS.has(key)) {
|
||||
return fieldGroupMeta(
|
||||
"trial_tariff_settings",
|
||||
"settings_group_trial_tariff_settings",
|
||||
"Trial access",
|
||||
"settings_group_trial_tariff_settings_hint",
|
||||
"Trial duration, traffic limit, and Remnawave squads are also available on the Tariffs page."
|
||||
);
|
||||
}
|
||||
if (LEGACY_TARIFF_TRAFFIC_KEYS.has(key)) {
|
||||
return fieldGroupMeta(
|
||||
"legacy_tariff_traffic",
|
||||
"settings_group_legacy_tariff_traffic",
|
||||
"Legacy traffic packages",
|
||||
"settings_group_legacy_tariff_traffic_hint",
|
||||
"Packages used only when the JSON tariff catalog is not configured."
|
||||
);
|
||||
}
|
||||
return fieldGroupMeta(
|
||||
"legacy_tariff_periods",
|
||||
"settings_group_legacy_tariff_periods",
|
||||
"Legacy subscription periods",
|
||||
"settings_group_legacy_tariff_periods_hint",
|
||||
"Month toggles and prices used only by the old remnawave-tg-shop mode."
|
||||
);
|
||||
}
|
||||
|
||||
function semanticFieldGroup(section, group, field) {
|
||||
if (section?.id === "payments" && group?.id === "Platega") {
|
||||
return plategaSemanticGroup(field);
|
||||
}
|
||||
if (section?.id === "pricing") {
|
||||
return legacyTariffSemanticGroup(field);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -471,25 +432,6 @@
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet renderLegacyTariffsWarning()}
|
||||
<div class="admin-settings-warning" role="status">
|
||||
<TriangleAlert size={16} aria-hidden="true" />
|
||||
<div class="admin-settings-warning-copy">
|
||||
<strong>{at("settings_legacy_tariffs_warning_title", {}, "Legacy tariffs")}</strong>
|
||||
<p>
|
||||
{at(
|
||||
"settings_legacy_tariffs_warning_body",
|
||||
{},
|
||||
"These settings are ignored when tariffs are configured in the dedicated Tariffs section."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<a class="admin-settings-warning-link" href="/admin/tariffs">
|
||||
{at("settings_legacy_tariffs_warning_link", {}, "Open Tariffs")}
|
||||
</a>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet renderGroupedFields(section, group)}
|
||||
{@const fieldGroups = semanticFieldGroups(section, group)}
|
||||
{#if fieldGroups.length === 1 && !fieldGroups[0].titleKey}
|
||||
@@ -754,9 +696,6 @@
|
||||
{@const rootGroup = groups.find((g) => !g.label)}
|
||||
{@const labelGroups = groups.filter((g) => g.label)}
|
||||
<div class="admin-settings-fields">
|
||||
{#if section.id === "pricing"}
|
||||
{@render renderLegacyTariffsWarning()}
|
||||
{/if}
|
||||
{#if rootGroup}
|
||||
{#if rootGroup.webhook}
|
||||
{@render renderWebhookHint(rootGroup.webhook)}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<script>
|
||||
import { RefreshCw, Trash2, Plus, Save, TriangleAlert, X } from "$components/ui/icons.js";
|
||||
import {
|
||||
ChevronRight,
|
||||
RefreshCw,
|
||||
Trash2,
|
||||
Plus,
|
||||
Save,
|
||||
TriangleAlert,
|
||||
X,
|
||||
} from "$components/ui/icons.js";
|
||||
import { getContext, onMount } from "svelte";
|
||||
import {
|
||||
AdminBadge,
|
||||
@@ -7,7 +15,7 @@
|
||||
AdminEmptyState,
|
||||
AdminSelect,
|
||||
} from "$components/patterns/admin/index.js";
|
||||
import { Switch } from "$components/ui/primitives.js";
|
||||
import { Accordion, Switch } from "$components/ui/primitives.js";
|
||||
|
||||
export let at;
|
||||
export let fmtMoney;
|
||||
@@ -68,6 +76,7 @@
|
||||
}));
|
||||
|
||||
let selectedTrialSquad = "";
|
||||
let tariffSettingsOpen = [];
|
||||
|
||||
function tariffName(tariff) {
|
||||
return tariff?.names?.ru || tariff?.names?.en || tariff?.key || "—";
|
||||
@@ -434,115 +443,138 @@
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="admin-card admin-tariff-settings-card">
|
||||
<header class="admin-card-head">
|
||||
<div>
|
||||
<h3>{at("tariffs_legacy_title", {}, "Legacy tariff compatibility")}</h3>
|
||||
<small>
|
||||
{at(
|
||||
"tariffs_legacy_subtitle",
|
||||
{},
|
||||
"Old remnawave-tg-shop periods and traffic packages used only when the JSON tariff catalog is not configured."
|
||||
)}
|
||||
</small>
|
||||
</div>
|
||||
<div class="admin-editor-section-actions">
|
||||
{#if legacyDirtyCount}
|
||||
<AdminBadge variant="warning">
|
||||
<Accordion.Root
|
||||
type="multiple"
|
||||
bind:value={tariffSettingsOpen}
|
||||
class="admin-accordion admin-tariff-settings-accordion"
|
||||
>
|
||||
<Accordion.Item
|
||||
value="legacy-tariffs"
|
||||
class="admin-accordion-item admin-card admin-tariff-settings-card"
|
||||
>
|
||||
<Accordion.Header class="admin-accordion-header">
|
||||
<Accordion.Trigger class="admin-accordion-trigger">
|
||||
<span class="admin-accordion-title">
|
||||
{at("tariffs_legacy_title", {}, "Совместимость с legacy-тарифами")}
|
||||
</span>
|
||||
<span class="admin-accordion-meta">
|
||||
{at(
|
||||
"settings_dirty_count",
|
||||
{ count: legacyDirtyCount },
|
||||
`Changes: ${legacyDirtyCount}`
|
||||
)}
|
||||
</AdminBadge>
|
||||
<AdminButton
|
||||
size="sm"
|
||||
variant="primary"
|
||||
onclick={saveTariffSettings}
|
||||
disabled={settingsSaving}
|
||||
>
|
||||
<Save size={13} />
|
||||
{settingsSaving ? at("btn_saving", {}, "Saving...") : at("btn_save", {}, "Save")}
|
||||
</AdminButton>
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
<div class="admin-card-body">
|
||||
<div class="admin-settings-warning" role="status">
|
||||
<TriangleAlert size={16} aria-hidden="true" />
|
||||
<div class="admin-settings-warning-copy">
|
||||
<strong>{at("settings_legacy_tariffs_warning_title", {}, "Legacy tariffs")}</strong>
|
||||
<p>
|
||||
{at(
|
||||
"settings_legacy_tariffs_warning_body",
|
||||
"tariffs_legacy_subtitle",
|
||||
{},
|
||||
"These settings are ignored when tariffs are configured in the dedicated Tariffs section."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-legacy-tariff-table">
|
||||
<div class="admin-legacy-tariff-row admin-legacy-tariff-head">
|
||||
<span>{at("tariffs_legacy_period", {}, "Period")}</span>
|
||||
<span>{at("tariffs_legacy_enabled", {}, "Enabled")}</span>
|
||||
<span>{at("payment_rub", {}, "RUB")}</span>
|
||||
<span>{at("payment_stars", {}, "Stars")}</span>
|
||||
</div>
|
||||
{#each LEGACY_PERIODS as [months, enabledKey, rubKey, starsKey]}
|
||||
<div class="admin-legacy-tariff-row">
|
||||
<strong>{months} {at("months_short", {}, "mo")}</strong>
|
||||
<div class="admin-setting-switch">
|
||||
<Switch.Root
|
||||
checked={boolValue(enabledKey)}
|
||||
onCheckedChange={(checked) => setSetting(enabledKey, checked)}
|
||||
class="admin-switch-root"
|
||||
"Старые периоды и пакеты трафика remnawave-tg-shop, которые используются только без JSON-каталога."
|
||||
)}{#if legacyDirtyCount}
|
||||
· {at(
|
||||
"settings_dirty_count",
|
||||
{ count: legacyDirtyCount },
|
||||
`Изменений: ${legacyDirtyCount}`
|
||||
)}{/if}
|
||||
</span>
|
||||
<ChevronRight size={16} class="admin-accordion-chev" />
|
||||
</Accordion.Trigger>
|
||||
</Accordion.Header>
|
||||
<Accordion.Content class="admin-accordion-content">
|
||||
<div class="admin-card-body">
|
||||
{#if legacyDirtyCount}
|
||||
<div class="admin-editor-section-actions admin-tariff-settings-save-row">
|
||||
<AdminBadge variant="warning">
|
||||
{at(
|
||||
"settings_dirty_count",
|
||||
{ count: legacyDirtyCount },
|
||||
`Изменений: ${legacyDirtyCount}`
|
||||
)}
|
||||
</AdminBadge>
|
||||
<AdminButton
|
||||
size="sm"
|
||||
variant="primary"
|
||||
onclick={saveTariffSettings}
|
||||
disabled={settingsSaving}
|
||||
>
|
||||
<Switch.Thumb class="admin-switch-thumb" />
|
||||
</Switch.Root>
|
||||
<Save size={13} />
|
||||
{settingsSaving
|
||||
? at("btn_saving", {}, "Сохранение...")
|
||||
: at("btn_save", {}, "Сохранить")}
|
||||
</AdminButton>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="admin-settings-warning" role="status">
|
||||
<TriangleAlert size={16} aria-hidden="true" />
|
||||
<div class="admin-settings-warning-copy">
|
||||
<strong>{at("settings_legacy_tariffs_warning_title", {}, "Legacy tariffs")}</strong>
|
||||
<p>
|
||||
{at(
|
||||
"settings_legacy_tariffs_warning_body",
|
||||
{},
|
||||
"These settings are ignored when tariffs are configured in the dedicated Tariffs section."
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value={valueForKey(rubKey)}
|
||||
oninput={(event) => setSetting(rubKey, event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value={valueForKey(starsKey)}
|
||||
oninput={(event) => setSetting(starsKey, event.currentTarget.value)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="admin-form-row admin-form-row-2 admin-legacy-traffic-row">
|
||||
<label class="admin-field-label admin-field-label-compact">
|
||||
<span>{at("tariffs_legacy_traffic_packages", {}, "Traffic packages")}</span>
|
||||
<small>{at("tariffs_legacy_traffic_hint", {}, "Format: 10:199,50:799")}</small>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
value={valueForKey("TRAFFIC_PACKAGES")}
|
||||
oninput={(event) => setSetting("TRAFFIC_PACKAGES", event.currentTarget.value)}
|
||||
/>
|
||||
</label>
|
||||
<label class="admin-field-label admin-field-label-compact">
|
||||
<span>{at("tariffs_legacy_stars_traffic_packages", {}, "Traffic packages, Stars")}</span>
|
||||
<small>{at("tariffs_legacy_traffic_hint", {}, "Format: 10:199,50:799")}</small>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
value={valueForKey("STARS_TRAFFIC_PACKAGES")}
|
||||
oninput={(event) => setSetting("STARS_TRAFFIC_PACKAGES", event.currentTarget.value)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<div class="admin-legacy-tariff-table">
|
||||
<div class="admin-legacy-tariff-row admin-legacy-tariff-head">
|
||||
<span>{at("tariffs_legacy_period", {}, "Period")}</span>
|
||||
<span>{at("tariffs_legacy_enabled", {}, "Enabled")}</span>
|
||||
<span>{at("payment_rub", {}, "RUB")}</span>
|
||||
<span>{at("payment_stars", {}, "Stars")}</span>
|
||||
</div>
|
||||
{#each LEGACY_PERIODS as [months, enabledKey, rubKey, starsKey]}
|
||||
<div class="admin-legacy-tariff-row">
|
||||
<strong>{months} {at("months_short", {}, "mo")}</strong>
|
||||
<div class="admin-setting-switch">
|
||||
<Switch.Root
|
||||
checked={boolValue(enabledKey)}
|
||||
onCheckedChange={(checked) => setSetting(enabledKey, checked)}
|
||||
class="admin-switch-root"
|
||||
>
|
||||
<Switch.Thumb class="admin-switch-thumb" />
|
||||
</Switch.Root>
|
||||
</div>
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value={valueForKey(rubKey)}
|
||||
oninput={(event) => setSetting(rubKey, event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
value={valueForKey(starsKey)}
|
||||
oninput={(event) => setSetting(starsKey, event.currentTarget.value)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="admin-form-row admin-form-row-2 admin-legacy-traffic-row">
|
||||
<label class="admin-field-label admin-field-label-compact">
|
||||
<span>{at("tariffs_legacy_traffic_packages", {}, "Traffic packages")}</span>
|
||||
<small>{at("tariffs_legacy_traffic_hint", {}, "Format: 10:199,50:799")}</small>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
value={valueForKey("TRAFFIC_PACKAGES")}
|
||||
oninput={(event) => setSetting("TRAFFIC_PACKAGES", event.currentTarget.value)}
|
||||
/>
|
||||
</label>
|
||||
<label class="admin-field-label admin-field-label-compact">
|
||||
<span
|
||||
>{at("tariffs_legacy_stars_traffic_packages", {}, "Traffic packages, Stars")}</span
|
||||
>
|
||||
<small>{at("tariffs_legacy_traffic_hint", {}, "Format: 10:199,50:799")}</small>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
value={valueForKey("STARS_TRAFFIC_PACKAGES")}
|
||||
oninput={(event) => setSetting("STARS_TRAFFIC_PACKAGES", event.currentTarget.value)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
{/if}
|
||||
|
||||
@@ -2822,6 +2822,11 @@
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.admin-tariff-settings-save-row {
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.admin-field-label-compact {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@@ -1305,6 +1305,43 @@
|
||||
"admin_tariffs_stat_disabled": "Disabled",
|
||||
"admin_tariffs_stat_disabled_hint": "Hidden from showcase",
|
||||
"admin_tariffs_catalog_empty": "The catalog is empty. Add your first tariff; a catalog JSON file will be created after saving.",
|
||||
"admin_tariffs_trial_title": "Trial access",
|
||||
"admin_tariffs_trial_subtitle": "Configure trial duration, traffic limit, and Remnawave squads from the tariff page.",
|
||||
"admin_tariffs_trial_enabled": "Trial enabled",
|
||||
"admin_tariffs_trial_days": "Duration, days",
|
||||
"admin_tariffs_trial_traffic": "Traffic limit, GB",
|
||||
"admin_tariffs_trial_strategy": "Traffic reset strategy",
|
||||
"admin_tariffs_trial_squads": "Trial Internal Squads",
|
||||
"admin_tariffs_trial_squads_hint": "These squads are applied when trial is activated. Empty value falls back to USER_SQUAD_UUIDS.",
|
||||
"admin_tariffs_trial_add_squad": "Add squad from panel",
|
||||
"admin_tariffs_legacy_title": "Legacy tariff compatibility",
|
||||
"admin_tariffs_legacy_subtitle": "Old remnawave-tg-shop periods and traffic packages used only when the JSON tariff catalog is not configured.",
|
||||
"admin_tariffs_legacy_period": "Period",
|
||||
"admin_tariffs_legacy_enabled": "Enabled",
|
||||
"admin_tariffs_legacy_traffic_packages": "Traffic packages",
|
||||
"admin_tariffs_legacy_stars_traffic_packages": "Traffic packages, Stars",
|
||||
"admin_tariffs_legacy_traffic_hint": "Format: 10:199,50:799",
|
||||
"admin_tariff_tab_general": "General",
|
||||
"admin_tariff_tab_pricing": "Prices",
|
||||
"admin_tariff_tab_topup": "Top-ups",
|
||||
"admin_tariff_tab_premium": "Premium",
|
||||
"admin_tariff_tab_hwid": "Devices",
|
||||
"admin_payment_rub": "Ruble payments",
|
||||
"admin_payment_stars": "Telegram Stars payments",
|
||||
"admin_appearance_theme_accent": "Accent",
|
||||
"admin_settings_icon_empty": "Default icon",
|
||||
"admin_settings_icon_default_value": "Default: {icon}",
|
||||
"admin_settings_icon_picker_title": "Choose icon",
|
||||
"admin_settings_icon_current": "Current icon",
|
||||
"admin_settings_icon_use_default": "Use default",
|
||||
"admin_search": "Search",
|
||||
"admin_clear": "Clear",
|
||||
"admin_save": "Save",
|
||||
"admin_saving": "Saving...",
|
||||
"admin_tariff_placeholder_name_en": "Standard",
|
||||
"admin_tariff_placeholder_desc_en": "Base server pool",
|
||||
"admin_tariff_placeholder_premium_name_en": "Premium servers",
|
||||
"admin_tariff_label_premium_squads": "Premium Internal Squads",
|
||||
"admin_themes_catalog_title": "Web App themes",
|
||||
"admin_themes_catalog_sub": "Select the current theme from a card; edit appearance through files in the theme folder",
|
||||
"admin_themes_use_primary_accent": "Use primary accent color",
|
||||
|
||||
+42
-5
@@ -1107,12 +1107,12 @@
|
||||
"admin_id": "ID",
|
||||
"admin_status_default": "По умолчанию",
|
||||
"admin_status_current": "Текущая",
|
||||
"admin_tariff_squads": "Squads",
|
||||
"admin_tariff_premium": "Premium",
|
||||
"admin_settings_badge_secret": "Secret",
|
||||
"admin_tariff_squads": "Сквады",
|
||||
"admin_tariff_premium": "Премиум",
|
||||
"admin_settings_badge_secret": "Секрет",
|
||||
"admin_settings_secret_configured": "Секрет задан",
|
||||
"admin_settings_secret_empty": "Не задан",
|
||||
"admin_settings_badge_override": "Override",
|
||||
"admin_settings_badge_override": "Оверрайд",
|
||||
"admin_status_limited": "Ограничен",
|
||||
"admin_status_expired": "Истёк",
|
||||
"admin_status_disabled": "Выключен",
|
||||
@@ -1292,7 +1292,7 @@
|
||||
"admin_collapse_all": "Свернуть всё",
|
||||
"admin_expand_all": "Развернуть всё",
|
||||
"admin_settings_params_count": "{count} параметров",
|
||||
"admin_settings_overridden_count": "{count} override",
|
||||
"admin_settings_overridden_count": "Оверрайдов: {count}",
|
||||
"admin_settings_fields_count": "{count} полей",
|
||||
"admin_settings_field_log_admin_actions_label": "Логировать действия администраторов",
|
||||
"admin_settings_field_log_admin_actions_description": "Если выключено, события от пользователей из ADMIN_IDS не записываются в message logs.",
|
||||
@@ -1305,6 +1305,43 @@
|
||||
"admin_tariffs_stat_disabled": "Отключено",
|
||||
"admin_tariffs_stat_disabled_hint": "Скрыто с витрины",
|
||||
"admin_tariffs_catalog_empty": "Каталог пуст. Добавьте первый тариф, после сохранения будет создан JSON-файл каталога.",
|
||||
"admin_tariffs_trial_title": "Пробный период",
|
||||
"admin_tariffs_trial_subtitle": "Настройки длительности, лимита трафика и Remnawave-сквадов для триала.",
|
||||
"admin_tariffs_trial_enabled": "Триал включён",
|
||||
"admin_tariffs_trial_days": "Длительность, дней",
|
||||
"admin_tariffs_trial_traffic": "Лимит трафика, GB",
|
||||
"admin_tariffs_trial_strategy": "Стратегия сброса трафика",
|
||||
"admin_tariffs_trial_squads": "Internal Squads для триала",
|
||||
"admin_tariffs_trial_squads_hint": "Эти сквады применяются при активации триала. Если поле пустое, используются USER_SQUAD_UUIDS.",
|
||||
"admin_tariffs_trial_add_squad": "Добавить сквад из панели",
|
||||
"admin_tariffs_legacy_title": "Совместимость с legacy-тарифами",
|
||||
"admin_tariffs_legacy_subtitle": "Старые периоды и пакеты трафика remnawave-tg-shop, которые используются только без JSON-каталога.",
|
||||
"admin_tariffs_legacy_period": "Период",
|
||||
"admin_tariffs_legacy_enabled": "Включён",
|
||||
"admin_tariffs_legacy_traffic_packages": "Пакеты трафика",
|
||||
"admin_tariffs_legacy_stars_traffic_packages": "Пакеты трафика, Stars",
|
||||
"admin_tariffs_legacy_traffic_hint": "Формат: 10:199,50:799",
|
||||
"admin_tariff_tab_general": "Основное",
|
||||
"admin_tariff_tab_pricing": "Цены",
|
||||
"admin_tariff_tab_topup": "Докупки",
|
||||
"admin_tariff_tab_premium": "Премиум",
|
||||
"admin_tariff_tab_hwid": "Устройства",
|
||||
"admin_payment_rub": "Оплата рублями",
|
||||
"admin_payment_stars": "Оплата Telegram Stars",
|
||||
"admin_appearance_theme_accent": "Акцент",
|
||||
"admin_settings_icon_empty": "Иконка по умолчанию",
|
||||
"admin_settings_icon_default_value": "По умолчанию: {icon}",
|
||||
"admin_settings_icon_picker_title": "Выбор иконки",
|
||||
"admin_settings_icon_current": "Текущая иконка",
|
||||
"admin_settings_icon_use_default": "Использовать по умолчанию",
|
||||
"admin_search": "Поиск",
|
||||
"admin_clear": "Очистить",
|
||||
"admin_save": "Сохранить",
|
||||
"admin_saving": "Сохранение...",
|
||||
"admin_tariff_placeholder_name_en": "Standard",
|
||||
"admin_tariff_placeholder_desc_en": "Base server pool",
|
||||
"admin_tariff_placeholder_premium_name_en": "Premium servers",
|
||||
"admin_tariff_label_premium_squads": "Internal Squads премиума",
|
||||
"admin_themes_catalog_title": "Темы Web App",
|
||||
"admin_themes_catalog_sub": "Текущая тема выбирается карточкой; внешний вид редактируется файлами в папке темы",
|
||||
"admin_themes_use_primary_accent": "Протягивать акцентный цвет",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from bot.app.web.admin_settings_manifest import manifest_payload
|
||||
@@ -31,6 +32,51 @@ SUBSCRIPTION_GUIDE_SETTINGS = (
|
||||
"SUBSCRIPTION_PAGE_CONFIG_JSON",
|
||||
)
|
||||
|
||||
ADMIN_TARIFF_SETTINGS_PAGE_KEYS = {
|
||||
"admin_tariffs_trial_title",
|
||||
"admin_tariffs_trial_subtitle",
|
||||
"admin_tariffs_trial_enabled",
|
||||
"admin_tariffs_trial_days",
|
||||
"admin_tariffs_trial_traffic",
|
||||
"admin_tariffs_trial_strategy",
|
||||
"admin_tariffs_trial_squads",
|
||||
"admin_tariffs_trial_squads_hint",
|
||||
"admin_tariffs_trial_add_squad",
|
||||
"admin_tariffs_legacy_title",
|
||||
"admin_tariffs_legacy_subtitle",
|
||||
"admin_tariffs_legacy_period",
|
||||
"admin_tariffs_legacy_enabled",
|
||||
"admin_tariffs_legacy_traffic_packages",
|
||||
"admin_tariffs_legacy_stars_traffic_packages",
|
||||
"admin_tariffs_legacy_traffic_hint",
|
||||
"admin_payment_rub",
|
||||
"admin_payment_stars",
|
||||
}
|
||||
|
||||
ADMIN_VISIBLE_RU_KEYS = {
|
||||
"admin_appearance_theme_accent",
|
||||
"admin_clear",
|
||||
"admin_save",
|
||||
"admin_saving",
|
||||
"admin_search",
|
||||
"admin_settings_badge_override",
|
||||
"admin_settings_badge_secret",
|
||||
"admin_settings_icon_current",
|
||||
"admin_settings_icon_default_value",
|
||||
"admin_settings_icon_empty",
|
||||
"admin_settings_icon_picker_title",
|
||||
"admin_settings_icon_use_default",
|
||||
"admin_tariff_label_premium_squads",
|
||||
"admin_tariff_premium",
|
||||
"admin_tariff_squads",
|
||||
"admin_tariff_tab_premium",
|
||||
}
|
||||
|
||||
ADMIN_AT_SIMPLE_FALLBACK_RE = re.compile(
|
||||
r"""\bat\(\s*["'`](?P<key>[^"'`$]+)["'`]\s*,\s*\{[^)]*?\}\s*,\s*["'`](?P<fallback>[^"'`$]*)["'`]""",
|
||||
re.DOTALL,
|
||||
)
|
||||
|
||||
|
||||
def _manifest_by_key() -> dict[str, dict]:
|
||||
return {item["key"]: item for item in manifest_payload()}
|
||||
@@ -135,3 +181,33 @@ def test_platega_settings_share_one_admin_subsection():
|
||||
|
||||
assert platega_keys
|
||||
assert {manifest[key]["subsection"] for key in platega_keys} == {"Platega"}
|
||||
|
||||
|
||||
def test_tariff_settings_page_i18n_keys_exist():
|
||||
for language in ("ru", "en"):
|
||||
messages = _locale(language)
|
||||
assert ADMIN_TARIFF_SETTINGS_PAGE_KEYS <= messages.keys()
|
||||
|
||||
|
||||
def test_visible_admin_russian_labels_do_not_fall_back_to_english():
|
||||
messages = _locale("ru")
|
||||
|
||||
for key in ADMIN_VISIBLE_RU_KEYS:
|
||||
assert key in messages
|
||||
|
||||
missing_latin_fallbacks = []
|
||||
for file_path in (REPO_ROOT / "frontend" / "src" / "admin").rglob("*"):
|
||||
if file_path.suffix not in {".svelte", ".js"}:
|
||||
continue
|
||||
text = file_path.read_text(encoding="utf-8")
|
||||
for match in ADMIN_AT_SIMPLE_FALLBACK_RE.finditer(text):
|
||||
locale_key = f"admin_{match.group('key')}"
|
||||
fallback = match.group("fallback")
|
||||
if locale_key in messages:
|
||||
continue
|
||||
has_latin = any("A" <= char <= "Z" or "a" <= char <= "z" for char in fallback)
|
||||
has_cyrillic = any("А" <= char <= "я" or char in "Ёё" for char in fallback)
|
||||
if has_latin and not has_cyrillic:
|
||||
missing_latin_fallbacks.append((locale_key, fallback, str(file_path)))
|
||||
|
||||
assert missing_latin_fallbacks == []
|
||||
|
||||
Reference in New Issue
Block a user