diff --git a/app/handlers/subscription.py b/app/handlers/subscription.py
index 9454217b..bb68b9a6 100644
--- a/app/handlers/subscription.py
+++ b/app/handlers/subscription.py
@@ -1,7 +1,9 @@
+import base64
import json
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Any, Tuple, Optional
+from urllib.parse import quote
from aiogram import Dispatcher, types, F
from aiogram.fsm.context import FSMContext
@@ -5556,6 +5558,20 @@ async def handle_device_guide(
+ f"\n{subscription_link}\n\n"
)
+ installation_description = get_step_description(featured_app, "installationStep", db_user.language)
+ add_description = get_step_description(featured_app, "addSubscriptionStep", db_user.language)
+ connect_description = get_step_description(featured_app, "connectAndUseStep", db_user.language)
+ additional_before_text = format_additional_section(
+ featured_app.get("additionalBeforeAddSubscriptionStep"),
+ texts,
+ db_user.language,
+ )
+ additional_after_text = format_additional_section(
+ featured_app.get("additionalAfterAddSubscriptionStep"),
+ texts,
+ db_user.language,
+ )
+
guide_text = (
texts.t(
"SUBSCRIPTION_DEVICE_GUIDE_TITLE",
@@ -5566,17 +5582,26 @@ async def handle_device_guide(
+ texts.t(
"SUBSCRIPTION_DEVICE_FEATURED_APP",
"π Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡΠ΅ΠΌΠΎΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅: {app_name}",
- ).format(app_name=featured_app['name'])
- + "\n\n"
- + texts.t("SUBSCRIPTION_DEVICE_STEP_INSTALL_TITLE", "Π¨Π°Π³ 1 - Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ°:")
- + f"\n{featured_app['installationStep']['description'][db_user.language]}\n\n"
- + texts.t("SUBSCRIPTION_DEVICE_STEP_ADD_TITLE", "Π¨Π°Π³ 2 - ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ:")
- + f"\n{featured_app['addSubscriptionStep']['description'][db_user.language]}\n\n"
- + texts.t("SUBSCRIPTION_DEVICE_STEP_CONNECT_TITLE", "Π¨Π°Π³ 3 - ΠΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅:")
- + f"\n{featured_app['connectAndUseStep']['description'][db_user.language]}\n\n"
- + texts.t("SUBSCRIPTION_DEVICE_HOW_TO_TITLE", "π‘ ΠΠ°ΠΊ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡ:")
- + "\n"
- + "\n".join(
+ ).format(app_name=featured_app.get('name', ''))
+ )
+
+ guide_text += "\n\n" + texts.t("SUBSCRIPTION_DEVICE_STEP_INSTALL_TITLE", "Π¨Π°Π³ 1 - Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ°:")
+ if installation_description:
+ guide_text += f"\n{installation_description}"
+
+ if additional_before_text:
+ guide_text += f"\n\n{additional_before_text}"
+
+ guide_text += "\n\n" + texts.t("SUBSCRIPTION_DEVICE_STEP_ADD_TITLE", "Π¨Π°Π³ 2 - ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ:")
+ if add_description:
+ guide_text += f"\n{add_description}"
+
+ guide_text += "\n\n" + texts.t("SUBSCRIPTION_DEVICE_STEP_CONNECT_TITLE", "Π¨Π°Π³ 3 - ΠΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅:")
+ if connect_description:
+ guide_text += f"\n{connect_description}"
+
+ guide_text += "\n\n" + texts.t("SUBSCRIPTION_DEVICE_HOW_TO_TITLE", "π‘ ΠΠ°ΠΊ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡ:")
+ guide_text += "\n" + "\n".join(
[
texts.t(
"SUBSCRIPTION_DEVICE_HOW_TO_STEP1",
@@ -5584,7 +5609,7 @@ async def handle_device_guide(
),
texts.t(
"SUBSCRIPTION_DEVICE_HOW_TO_STEP2",
- "2. Π‘ΠΊΠΎΠΏΠΈΡΡΠΉΡΠ΅ ΡΡΡΠ»ΠΊΡ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ (Π½Π°ΠΆΠΌΠΈΡΠ΅ Π½Π° Π½Π΅Ρ)",
+ "2. ΠΠ°ΠΆΠΌΠΈΡΠ΅ ΠΊΠ½ΠΎΠΏΠΊΡ \"ΠΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡΡΡ\" Π½ΠΈΠΆΠ΅",
),
texts.t(
"SUBSCRIPTION_DEVICE_HOW_TO_STEP3",
@@ -5596,7 +5621,9 @@ async def handle_device_guide(
),
]
)
- )
+
+ if additional_after_text:
+ guide_text += f"\n\n{additional_after_text}"
await callback.message.edit_text(
guide_text,
@@ -5691,31 +5718,46 @@ async def handle_specific_app_guide(
+ f"\n{subscription_link}\n\n"
)
+ installation_description = get_step_description(app, "installationStep", db_user.language)
+ add_description = get_step_description(app, "addSubscriptionStep", db_user.language)
+ connect_description = get_step_description(app, "connectAndUseStep", db_user.language)
+ additional_before_text = format_additional_section(
+ app.get("additionalBeforeAddSubscriptionStep"),
+ texts,
+ db_user.language,
+ )
+ additional_after_text = format_additional_section(
+ app.get("additionalAfterAddSubscriptionStep"),
+ texts,
+ db_user.language,
+ )
+
guide_text = (
texts.t(
"SUBSCRIPTION_SPECIFIC_APP_TITLE",
"π± {app_name} - {device_name}",
- ).format(app_name=app['name'], device_name=get_device_name(device_type, db_user.language))
+ ).format(app_name=app.get('name', ''), device_name=get_device_name(device_type, db_user.language))
+ "\n\n"
+ link_section
- + texts.t("SUBSCRIPTION_DEVICE_STEP_INSTALL_TITLE", "Π¨Π°Π³ 1 - Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ°:")
- + f"\n{app['installationStep']['description'][db_user.language]}\n\n"
- + texts.t("SUBSCRIPTION_DEVICE_STEP_ADD_TITLE", "Π¨Π°Π³ 2 - ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ:")
- + f"\n{app['addSubscriptionStep']['description'][db_user.language]}\n\n"
- + texts.t("SUBSCRIPTION_DEVICE_STEP_CONNECT_TITLE", "Π¨Π°Π³ 3 - ΠΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅:")
- + f"\n{app['connectAndUseStep']['description'][db_user.language]}"
)
- if 'additionalAfterAddSubscriptionStep' in app:
- additional = app['additionalAfterAddSubscriptionStep']
- guide_text += (
- "\n\n"
- + texts.t(
- "SUBSCRIPTION_ADDITIONAL_STEP_TITLE",
- "{title}:",
- ).format(title=additional['title'][db_user.language])
- + f"\n{additional['description'][db_user.language]}"
- )
+ guide_text += texts.t("SUBSCRIPTION_DEVICE_STEP_INSTALL_TITLE", "Π¨Π°Π³ 1 - Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ°:")
+ if installation_description:
+ guide_text += f"\n{installation_description}"
+
+ if additional_before_text:
+ guide_text += f"\n\n{additional_before_text}"
+
+ guide_text += "\n\n" + texts.t("SUBSCRIPTION_DEVICE_STEP_ADD_TITLE", "Π¨Π°Π³ 2 - ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ:")
+ if add_description:
+ guide_text += f"\n{add_description}"
+
+ guide_text += "\n\n" + texts.t("SUBSCRIPTION_DEVICE_STEP_CONNECT_TITLE", "Π¨Π°Π³ 3 - ΠΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅:")
+ if connect_description:
+ guide_text += f"\n{connect_description}"
+
+ if additional_after_text:
+ guide_text += f"\n\n{additional_after_text}"
await callback.message.edit_text(
guide_text,
@@ -5858,14 +5900,119 @@ def load_app_config() -> Dict[str, Any]:
config_path = settings.get_app_config_path()
with open(config_path, 'r', encoding='utf-8') as f:
- return json.load(f)
+ data = json.load(f)
+ if isinstance(data, dict):
+ return data
+ logger.error("ΠΠ΅ΠΊΠΎΡΡΠ΅ΠΊΡΠ½ΡΠΉ ΡΠΎΡΠΌΠ°Ρ app-config.json: ΠΎΠΆΠΈΠ΄Π°Π΅ΡΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ")
except Exception as e:
logger.error(f"ΠΡΠΈΠ±ΠΊΠ° Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΠΊΠΎΠ½ΡΠΈΠ³Π° ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ: {e}")
- return {}
+
+ return {}
+
+
+def get_localized_value(values: Any, language: str, default_language: str = "en") -> str:
+ if not isinstance(values, dict):
+ return ""
+
+ candidates: List[str] = []
+ normalized_language = (language or "").strip().lower()
+
+ if normalized_language:
+ candidates.append(normalized_language)
+ if "-" in normalized_language:
+ candidates.append(normalized_language.split("-")[0])
+
+ default_language = (default_language or "").strip().lower()
+ if default_language and default_language not in candidates:
+ candidates.append(default_language)
+
+ for candidate in candidates:
+ if not candidate:
+ continue
+ value = values.get(candidate)
+ if isinstance(value, str) and value.strip():
+ return value
+
+ for value in values.values():
+ if isinstance(value, str) and value.strip():
+ return value
+
+ return ""
+
+
+def get_step_description(app: Dict[str, Any], step_key: str, language: str) -> str:
+ if not isinstance(app, dict):
+ return ""
+
+ step = app.get(step_key)
+ if not isinstance(step, dict):
+ return ""
+
+ description = step.get("description")
+ return get_localized_value(description, language)
+
+
+def format_additional_section(additional: Any, texts, language: str) -> str:
+ if not isinstance(additional, dict):
+ return ""
+
+ title = get_localized_value(additional.get("title"), language)
+ description = get_localized_value(additional.get("description"), language)
+
+ parts: List[str] = []
+
+ if title:
+ parts.append(
+ texts.t(
+ "SUBSCRIPTION_ADDITIONAL_STEP_TITLE",
+ "{title}:",
+ ).format(title=title)
+ )
+
+ if description:
+ parts.append(description)
+
+ return "\n".join(parts)
+
+
+def build_redirect_link(target_link: Optional[str], template: Optional[str]) -> Optional[str]:
+ if not target_link or not template:
+ return None
+
+ normalized_target = str(target_link).strip()
+ normalized_template = str(template).strip()
+
+ if not normalized_target or not normalized_template:
+ return None
+
+ encoded_target = quote(normalized_target, safe="")
+ result = normalized_template
+ replaced = False
+
+ replacements = [
+ ("{subscription_link}", encoded_target),
+ ("{link}", encoded_target),
+ ("{subscription_link_raw}", normalized_target),
+ ("{link_raw}", normalized_target),
+ ]
+
+ for placeholder, replacement in replacements:
+ if placeholder in result:
+ result = result.replace(placeholder, replacement)
+ replaced = True
+
+ if not replaced:
+ result = f"{result}{encoded_target}"
+
+ return result
def get_apps_for_device(device_type: str, language: str = "ru") -> List[Dict[str, Any]]:
- config = load_app_config()['platforms']
+ config = load_app_config()
+ platforms = config.get("platforms", {}) if isinstance(config, dict) else {}
+
+ if not isinstance(platforms, dict):
+ return []
device_mapping = {
'ios': 'ios',
@@ -5876,32 +6023,49 @@ def get_apps_for_device(device_type: str, language: str = "ru") -> List[Dict[str
}
config_key = device_mapping.get(device_type, device_type)
- return config.get(config_key, [])
+ apps = platforms.get(config_key, [])
+ return apps if isinstance(apps, list) else []
def get_device_name(device_type: str, language: str = "ru") -> str:
- if language == "en":
- names = {
- 'ios': 'iPhone/iPad',
- 'android': 'Android',
- 'windows': 'Windows',
- 'mac': 'macOS',
- 'tv': 'Android TV'
- }
- else:
- names = {
- 'ios': 'iPhone/iPad',
- 'android': 'Android',
- 'windows': 'Windows',
- 'mac': 'macOS',
- 'tv': 'Android TV'
- }
+ names = {
+ 'ios': 'iPhone/iPad',
+ 'android': 'Android',
+ 'windows': 'Windows',
+ 'mac': 'macOS',
+ 'tv': 'Android TV'
+ }
return names.get(device_type, device_type)
-def create_deep_link(app: Dict[str, Any], subscription_url: str) -> str:
- return subscription_url
+def create_deep_link(app: Dict[str, Any], subscription_url: str) -> Optional[str]:
+ if not subscription_url:
+ return None
+
+ if not isinstance(app, dict):
+ return subscription_url
+
+ scheme = str(app.get("urlScheme", "")).strip()
+ payload = subscription_url
+
+ if app.get("isNeedBase64Encoding"):
+ try:
+ payload = base64.b64encode(subscription_url.encode("utf-8")).decode("utf-8")
+ except Exception as exc:
+ logger.warning(
+ "ΠΠ΅ ΡΠ΄Π°Π»ΠΎΡΡ Π·Π°ΠΊΠΎΠ΄ΠΈΡΠΎΠ²Π°ΡΡ ΡΡΡΠ»ΠΊΡ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ Π² base64 Π΄Π»Ρ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ %s: %s",
+ app.get("id"),
+ exc,
+ )
+ payload = subscription_url
+
+ scheme_link = f"{scheme}{payload}" if scheme else None
+
+ template = settings.get_happ_cryptolink_redirect_template()
+ redirect_link = build_redirect_link(scheme_link, template) if scheme_link and template else None
+
+ return redirect_link or scheme_link or subscription_url
def get_reset_devices_confirm_keyboard(language: str = "ru") -> InlineKeyboardMarkup:
diff --git a/app/keyboards/inline.py b/app/keyboards/inline.py
index b6cb2212..9a051e75 100644
--- a/app/keyboards/inline.py
+++ b/app/keyboards/inline.py
@@ -17,6 +17,64 @@ import logging
logger = logging.getLogger(__name__)
+
+def _get_localized_value(values, language: str, default_language: str = "en") -> str:
+ if not isinstance(values, dict):
+ return ""
+
+ candidates = []
+ normalized_language = (language or "").strip().lower()
+
+ if normalized_language:
+ candidates.append(normalized_language)
+ if "-" in normalized_language:
+ candidates.append(normalized_language.split("-")[0])
+
+ default_language = (default_language or "").strip().lower()
+ if default_language and default_language not in candidates:
+ candidates.append(default_language)
+
+ for candidate in candidates:
+ if not candidate:
+ continue
+ value = values.get(candidate)
+ if isinstance(value, str) and value.strip():
+ return value
+
+ for value in values.values():
+ if isinstance(value, str) and value.strip():
+ return value
+
+ return ""
+
+
+def _build_additional_buttons(additional_section, language: str) -> List[InlineKeyboardButton]:
+ if not isinstance(additional_section, dict):
+ return []
+
+ buttons = additional_section.get("buttons")
+ if not isinstance(buttons, list):
+ return []
+
+ localized_buttons: List[InlineKeyboardButton] = []
+
+ for button in buttons:
+ if not isinstance(button, dict):
+ continue
+
+ button_text = _get_localized_value(button.get("buttonText"), language)
+ button_link = button.get("buttonLink")
+
+ if not button_text or not button_link:
+ continue
+
+ localized_buttons.append(
+ InlineKeyboardButton(text=button_text, url=button_link)
+ )
+
+ return localized_buttons
+
+
_LANGUAGE_DISPLAY_NAMES = {
"ru": "π·πΊ Π ΡΡΡΠΊΠΈΠΉ",
"en": "π¬π§ English",
@@ -1564,42 +1622,70 @@ def get_device_selection_keyboard(language: str = DEFAULT_LANGUAGE) -> InlineKey
def get_connection_guide_keyboard(
- subscription_url: str,
- app: dict,
+ subscription_url: str,
+ app: dict,
language: str = DEFAULT_LANGUAGE
) -> InlineKeyboardMarkup:
from app.handlers.subscription import create_deep_link
texts = get_texts(language)
-
+
keyboard = []
-
+
if 'installationStep' in app and 'buttons' in app['installationStep']:
app_buttons = []
for button in app['installationStep']['buttons']:
- button_text = button['buttonText'].get(language, button['buttonText']['en'])
+ button_text = _get_localized_value(button.get('buttonText'), language)
+ button_link = button.get('buttonLink')
+
+ if not button_text or not button_link:
+ continue
+
app_buttons.append(
- InlineKeyboardButton(text=f"π₯ {button_text}", url=button['buttonLink'])
+ InlineKeyboardButton(text=f"π₯ {button_text}", url=button_link)
)
if len(app_buttons) == 2:
keyboard.append(app_buttons)
app_buttons = []
-
+
if app_buttons:
keyboard.append(app_buttons)
-
- if settings.is_happ_cryptolink_mode():
- copy_button = InlineKeyboardButton(
- text=texts.t("COPY_SUBSCRIPTION_LINK", "π Π‘ΠΊΠΎΠΏΠΈΡΠΎΠ²Π°ΡΡ ΡΡΡΠ»ΠΊΡ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ"),
+
+ additional_before_buttons = _build_additional_buttons(
+ app.get('additionalBeforeAddSubscriptionStep'),
+ language,
+ )
+
+ for button in additional_before_buttons:
+ keyboard.append([button])
+
+ connect_link = create_deep_link(app, subscription_url)
+
+ if connect_link:
+ connect_button = InlineKeyboardButton(
+ text=texts.t("CONNECT_BUTTON", "π ΠΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡΡΡ"),
+ url=connect_link,
+ )
+ elif settings.is_happ_cryptolink_mode():
+ connect_button = InlineKeyboardButton(
+ text=texts.t("CONNECT_BUTTON", "π ΠΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡΡΡ"),
callback_data="open_subscription_link",
)
else:
- copy_button = InlineKeyboardButton(
- text=texts.t("COPY_SUBSCRIPTION_LINK", "π Π‘ΠΊΠΎΠΏΠΈΡΠΎΠ²Π°ΡΡ ΡΡΡΠ»ΠΊΡ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ"),
+ connect_button = InlineKeyboardButton(
+ text=texts.t("CONNECT_BUTTON", "π ΠΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡΡΡ"),
url=subscription_url,
)
- keyboard.append([copy_button])
-
+ keyboard.append([connect_button])
+
+ additional_after_buttons = _build_additional_buttons(
+ app.get('additionalAfterAddSubscriptionStep'),
+ language,
+ )
+
+ for button in additional_after_buttons:
+ keyboard.append([button])
+
keyboard.extend([
[
InlineKeyboardButton(text=texts.t("CHOOSE_ANOTHER_DEVICE", "π± ΠΡΠ±ΡΠ°ΡΡ Π΄ΡΡΠ³ΠΎΠ΅ ΡΡΡΡΠΎΠΉΡΡΠ²ΠΎ"), callback_data="subscription_connect")
@@ -1652,43 +1738,64 @@ def get_specific_app_keyboard(
) -> InlineKeyboardMarkup:
from app.handlers.subscription import create_deep_link
texts = get_texts(language)
-
+
keyboard = []
-
+
if 'installationStep' in app and 'buttons' in app['installationStep']:
app_buttons = []
for button in app['installationStep']['buttons']:
- button_text = button['buttonText'].get(language, button['buttonText']['en'])
+ button_text = _get_localized_value(button.get('buttonText'), language)
+ button_link = button.get('buttonLink')
+
+ if not button_text or not button_link:
+ continue
+
app_buttons.append(
- InlineKeyboardButton(text=f"π₯ {button_text}", url=button['buttonLink'])
+ InlineKeyboardButton(text=f"π₯ {button_text}", url=button_link)
)
if len(app_buttons) == 2:
keyboard.append(app_buttons)
app_buttons = []
-
+
if app_buttons:
keyboard.append(app_buttons)
-
- if settings.is_happ_cryptolink_mode():
- copy_button = InlineKeyboardButton(
- text=texts.t("COPY_SUBSCRIPTION_LINK", "π Π‘ΠΊΠΎΠΏΠΈΡΠΎΠ²Π°ΡΡ ΡΡΡΠ»ΠΊΡ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ"),
+
+ additional_before_buttons = _build_additional_buttons(
+ app.get('additionalBeforeAddSubscriptionStep'),
+ language,
+ )
+
+ for button in additional_before_buttons:
+ keyboard.append([button])
+
+ connect_link = create_deep_link(app, subscription_url)
+
+ if connect_link:
+ connect_button = InlineKeyboardButton(
+ text=texts.t("CONNECT_BUTTON", "π ΠΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡΡΡ"),
+ url=connect_link,
+ )
+ elif settings.is_happ_cryptolink_mode():
+ connect_button = InlineKeyboardButton(
+ text=texts.t("CONNECT_BUTTON", "π ΠΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡΡΡ"),
callback_data="open_subscription_link",
)
else:
- copy_button = InlineKeyboardButton(
- text=texts.t("COPY_SUBSCRIPTION_LINK", "π Π‘ΠΊΠΎΠΏΠΈΡΠΎΠ²Π°ΡΡ ΡΡΡΠ»ΠΊΡ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ"),
+ connect_button = InlineKeyboardButton(
+ text=texts.t("CONNECT_BUTTON", "π ΠΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡΡΡ"),
url=subscription_url,
)
- keyboard.append([copy_button])
-
- if 'additionalAfterAddSubscriptionStep' in app and 'buttons' in app['additionalAfterAddSubscriptionStep']:
- for button in app['additionalAfterAddSubscriptionStep']['buttons']:
- button_text = button['buttonText'].get(language, button['buttonText']['en'])
- keyboard.append([
- InlineKeyboardButton(text=button_text, url=button['buttonLink'])
- ])
-
+ keyboard.append([connect_button])
+
+ additional_after_buttons = _build_additional_buttons(
+ app.get('additionalAfterAddSubscriptionStep'),
+ language,
+ )
+
+ for button in additional_after_buttons:
+ keyboard.append([button])
+
keyboard.extend([
[
InlineKeyboardButton(text=texts.t("OTHER_APPS_BUTTON", "π ΠΡΡΠ³ΠΈΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ"), callback_data=f"app_list_{device_type}")
diff --git a/app/localization/locales/en.json b/app/localization/locales/en.json
index 8b1cdb3a..c1535d15 100644
--- a/app/localization/locales/en.json
+++ b/app/localization/locales/en.json
@@ -440,7 +440,7 @@
"SUBSCRIPTION_DEVICE_STEP_CONNECT_TITLE": "Step 3 - Connect:",
"SUBSCRIPTION_DEVICE_HOW_TO_TITLE": "π‘ How to connect:",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP1": "1. Install the app from the link above",
- "SUBSCRIPTION_DEVICE_HOW_TO_STEP2": "2. Copy the subscription link (tap on it)",
+ "SUBSCRIPTION_DEVICE_HOW_TO_STEP2": "2. Tap the \"Connect\" button below",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP3": "3. Open the app and paste the link",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP4": "4. Connect to a server",
"SUBSCRIPTION_APPS_TITLE": "π± Apps for {device_name}",
diff --git a/app/localization/locales/ru.json b/app/localization/locales/ru.json
index ca6ba4fd..2896470b 100644
--- a/app/localization/locales/ru.json
+++ b/app/localization/locales/ru.json
@@ -450,7 +450,7 @@
"SUBSCRIPTION_DEVICE_STEP_CONNECT_TITLE": "Π¨Π°Π³ 3 - ΠΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅:",
"SUBSCRIPTION_DEVICE_HOW_TO_TITLE": "π‘ ΠΠ°ΠΊ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡ:",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP1": "1. Π£ΡΡΠ°Π½ΠΎΠ²ΠΈΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΏΠΎ ΡΡΡΠ»ΠΊΠ΅ Π²ΡΡΠ΅",
- "SUBSCRIPTION_DEVICE_HOW_TO_STEP2": "2. Π‘ΠΊΠΎΠΏΠΈΡΡΠΉΡΠ΅ ΡΡΡΠ»ΠΊΡ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ (Π½Π°ΠΆΠΌΠΈΡΠ΅ Π½Π° Π½Π΅Ρ)",
+ "SUBSCRIPTION_DEVICE_HOW_TO_STEP2": "2. ΠΠ°ΠΆΠΌΠΈΡΠ΅ ΠΊΠ½ΠΎΠΏΠΊΡ \"ΠΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡΡΡ\" Π½ΠΈΠΆΠ΅",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP3": "3. ΠΡΠΊΡΠΎΠΉΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΈ Π²ΡΡΠ°Π²ΡΡΠ΅ ΡΡΡΠ»ΠΊΡ",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP4": "4. ΠΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΠ΅ΡΡ ΠΊ ΡΠ΅ΡΠ²Π΅ΡΡ",
"SUBSCRIPTION_APPS_TITLE": "π± ΠΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ Π΄Π»Ρ {device_name}",
diff --git a/locales/en.json b/locales/en.json
index e4b12469..aa17d574 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -480,7 +480,7 @@
"SUBSCRIPTION_DEVICE_STEP_CONNECT_TITLE": "Step 3 - Connect:",
"SUBSCRIPTION_DEVICE_HOW_TO_TITLE": "π‘ How to connect:",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP1": "1. Install the app from the link above",
- "SUBSCRIPTION_DEVICE_HOW_TO_STEP2": "2. Copy the subscription link (tap on it)",
+ "SUBSCRIPTION_DEVICE_HOW_TO_STEP2": "2. Tap the \"Connect\" button below",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP3": "3. Open the app and paste the link",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP4": "4. Connect to a server",
"SUBSCRIPTION_APPS_TITLE": "π± Apps for {device_name}",
diff --git a/locales/ru.json b/locales/ru.json
index 31d2704e..11666b7b 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -482,7 +482,7 @@
"SUBSCRIPTION_DEVICE_STEP_CONNECT_TITLE": "Π¨Π°Π³ 3 - ΠΠΎΠ΄ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅:",
"SUBSCRIPTION_DEVICE_HOW_TO_TITLE": "π‘ ΠΠ°ΠΊ ΠΏΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡ:",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP1": "1. Π£ΡΡΠ°Π½ΠΎΠ²ΠΈΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΏΠΎ ΡΡΡΠ»ΠΊΠ΅ Π²ΡΡΠ΅",
- "SUBSCRIPTION_DEVICE_HOW_TO_STEP2": "2. Π‘ΠΊΠΎΠΏΠΈΡΡΠΉΡΠ΅ ΡΡΡΠ»ΠΊΡ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ (Π½Π°ΠΆΠΌΠΈΡΠ΅ Π½Π° Π½Π΅Ρ)",
+ "SUBSCRIPTION_DEVICE_HOW_TO_STEP2": "2. ΠΠ°ΠΆΠΌΠΈΡΠ΅ ΠΊΠ½ΠΎΠΏΠΊΡ \"ΠΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΡΡΡ\" Π½ΠΈΠΆΠ΅",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP3": "3. ΠΡΠΊΡΠΎΠΉΡΠ΅ ΠΏΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ ΠΈ Π²ΡΡΠ°Π²ΡΡΠ΅ ΡΡΡΠ»ΠΊΡ",
"SUBSCRIPTION_DEVICE_HOW_TO_STEP4": "4. ΠΠΎΠ΄ΠΊΠ»ΡΡΠΈΡΠ΅ΡΡ ΠΊ ΡΠ΅ΡΠ²Π΅ΡΡ",
"SUBSCRIPTION_APPS_TITLE": "π± ΠΡΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΡ Π΄Π»Ρ {device_name}",