""" Email notification templates for different notification types. Supports multiple languages: ru, en, zh, ua """ from typing import Any from app.config import settings class EmailNotificationTemplates: """HTML email templates for user notifications.""" def __init__(self): self.service_name = settings.SMTP_FROM_NAME or 'VPN Service' self.cabinet_url = getattr(settings, 'CABINET_URL', '') def get_template( self, notification_type: 'NotificationType', language: str, context: dict[str, Any], ) -> dict[str, str] | None: """ Get email template for notification type. Args: notification_type: Type of notification language: Language code (ru, en, zh, ua) context: Context data for template rendering Returns: Dict with 'subject', 'body_html', and optionally 'body_text' """ # Import here to avoid circular imports from app.services.notification_delivery_service import NotificationType template_map = { NotificationType.BALANCE_TOPUP: self._balance_topup_template, NotificationType.BALANCE_CHANGE: self._balance_change_template, NotificationType.SUBSCRIPTION_EXPIRING: self._subscription_expiring_template, NotificationType.SUBSCRIPTION_EXPIRED: self._subscription_expired_template, NotificationType.SUBSCRIPTION_RENEWED: self._subscription_renewed_template, NotificationType.SUBSCRIPTION_ACTIVATED: self._subscription_activated_template, NotificationType.AUTOPAY_SUCCESS: self._autopay_success_template, NotificationType.AUTOPAY_FAILED: self._autopay_failed_template, NotificationType.AUTOPAY_INSUFFICIENT_FUNDS: self._autopay_insufficient_funds_template, NotificationType.DAILY_DEBIT: self._daily_debit_template, NotificationType.DAILY_INSUFFICIENT_FUNDS: self._daily_insufficient_funds_template, NotificationType.BAN_NOTIFICATION: self._ban_template, NotificationType.UNBAN_NOTIFICATION: self._unban_template, NotificationType.WARNING_NOTIFICATION: self._warning_template, NotificationType.REFERRAL_BONUS: self._referral_bonus_template, NotificationType.REFERRAL_REGISTERED: self._referral_registered_template, NotificationType.TRAFFIC_RESET: self._traffic_reset_template, NotificationType.PAYMENT_RECEIVED: self._payment_received_template, } template_func = template_map.get(notification_type) if not template_func: return None return template_func(language, context) def _get_base_template(self, content: str) -> str: """Wrap content in base HTML template.""" return f"""

{self.service_name}

{content}
""" def _get_cabinet_button(self, language: str) -> str: """Get cabinet link button HTML.""" if not self.cabinet_url: return '' texts = { 'ru': 'Открыть личный кабинет', 'en': 'Open Dashboard', 'zh': '打开控制面板', 'ua': 'Відкрити особистий кабінет', } text = texts.get(language, texts['en']) return f'

{text}

' # ============================================================================ # Balance Templates # ============================================================================ def _balance_topup_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for balance top-up notification.""" amount = context.get('formatted_amount', f'{context.get("amount_rubles", 0):.2f} ₽') balance = context.get('formatted_balance', f'{context.get("new_balance_rubles", 0):.2f} ₽') subjects = { 'ru': f'Баланс пополнен на {amount}', 'en': f'Balance topped up by {amount}', 'zh': f'余额已充值 {amount}', 'ua': f'Баланс поповнено на {amount}', } bodies = { 'ru': f"""

Баланс успешно пополнен!

Сумма пополнения: +{amount}

Текущий баланс: {balance}

Спасибо за использование нашего сервиса!

{self._get_cabinet_button(language)} """, 'en': f"""

Balance Successfully Topped Up!

Top-up amount: +{amount}

Current balance: {balance}

Thank you for using our service!

{self._get_cabinet_button(language)} """, 'zh': f"""

充值成功!

充值金额: +{amount}

当前余额: {balance}

感谢使用我们的服务!

{self._get_cabinet_button(language)} """, 'ua': f"""

Баланс успішно поповнено!

Сума поповнення: +{amount}

Поточний баланс: {balance}

Дякуємо за використання нашого сервісу!

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _balance_change_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for balance change notification.""" amount = context.get('formatted_amount', f'{context.get("amount_rubles", 0):.2f} ₽') balance = context.get('formatted_balance', f'{context.get("new_balance_rubles", 0):.2f} ₽') description = context.get('description', '') subjects = { 'ru': 'Изменение баланса', 'en': 'Balance Changed', 'zh': '余额变动', 'ua': 'Зміна балансу', } bodies = { 'ru': f"""

Изменение баланса

Сумма: {amount}

Текущий баланс: {balance}

{f'

Описание: {description}

' if description else ''}
{self._get_cabinet_button(language)} """, 'en': f"""

Balance Changed

Amount: {amount}

Current balance: {balance}

{f'

Description: {description}

' if description else ''}
{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } # ============================================================================ # Subscription Templates # ============================================================================ def _subscription_expiring_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for subscription expiring notification.""" days_left = context.get('days_left', 0) expires_at = context.get('expires_at', '') subjects = { 'ru': f'Подписка истекает через {days_left} дн.', 'en': f'Subscription expires in {days_left} day(s)', 'zh': f'订阅将在 {days_left} 天后到期', 'ua': f'Підписка закінчується через {days_left} дн.', } bodies = { 'ru': f"""

Подписка скоро истекает

Ваша подписка истекает через {days_left} дн.

Дата истечения: {expires_at}

Продлите подписку, чтобы не потерять доступ к сервису.

{self._get_cabinet_button(language)} """, 'en': f"""

Subscription Expiring Soon

Your subscription expires in {days_left} day(s).

Expiration date: {expires_at}

Renew your subscription to maintain access to our service.

{self._get_cabinet_button(language)} """, 'zh': f"""

订阅即将到期

您的订阅将在 {days_left} 天后到期。

到期日期: {expires_at}

请续订以保持对服务的访问。

{self._get_cabinet_button(language)} """, 'ua': f"""

Підписка скоро закінчується

Ваша підписка закінчується через {days_left} дн.

Дата закінчення: {expires_at}

Продовжіть підписку, щоб не втратити доступ до сервісу.

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _subscription_expired_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for subscription expired notification.""" subjects = { 'ru': 'Подписка истекла', 'en': 'Subscription Expired', 'zh': '订阅已到期', 'ua': 'Підписка закінчилась', } bodies = { 'ru': f"""

Подписка истекла

Ваша подписка истекла. Доступ к VPN отключён.

Оформите новую подписку, чтобы продолжить использование сервиса.

{self._get_cabinet_button(language)} """, 'en': f"""

Subscription Expired

Your subscription has expired. VPN access has been disabled.

Purchase a new subscription to continue using our service.

{self._get_cabinet_button(language)} """, 'zh': f"""

订阅已到期

您的订阅已到期。VPN访问已被禁用。

请购买新订阅以继续使用我们的服务。

{self._get_cabinet_button(language)} """, 'ua': f"""

Підписка закінчилась

Ваша підписка закінчилась. Доступ до VPN вимкнено.

Оформіть нову підписку, щоб продовжити використання сервісу.

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _subscription_renewed_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for subscription renewed notification.""" new_expires_at = context.get('new_expires_at', '') subjects = { 'ru': 'Подписка продлена', 'en': 'Subscription Renewed', 'zh': '订阅已续订', 'ua': 'Підписку продовжено', } bodies = { 'ru': f"""

Подписка успешно продлена!

Ваша подписка была успешно продлена.

Новая дата истечения: {new_expires_at}

Спасибо за использование нашего сервиса!

{self._get_cabinet_button(language)} """, 'en': f"""

Subscription Successfully Renewed!

Your subscription has been successfully renewed.

New expiration date: {new_expires_at}

Thank you for using our service!

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _subscription_activated_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for subscription activated notification.""" expires_at = context.get('expires_at', '') subjects = { 'ru': 'Подписка активирована', 'en': 'Subscription Activated', 'zh': '订阅已激活', 'ua': 'Підписку активовано', } bodies = { 'ru': f"""

Подписка активирована!

Ваша VPN подписка успешно активирована.

Действует до: {expires_at}

Теперь вы можете пользоваться VPN сервисом.

{self._get_cabinet_button(language)} """, 'en': f"""

Subscription Activated!

Your VPN subscription has been successfully activated.

Valid until: {expires_at}

You can now use the VPN service.

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } # ============================================================================ # Autopay Templates # ============================================================================ def _autopay_success_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for successful autopay notification.""" amount = context.get('formatted_amount', f'{context.get("amount_rubles", 0):.2f} ₽') new_expires_at = context.get('new_expires_at', '') subjects = { 'ru': 'Автопродление выполнено', 'en': 'Auto-renewal Successful', 'zh': '自动续订成功', 'ua': 'Автопродовження виконано', } bodies = { 'ru': f"""

Автопродление выполнено

Ваша подписка была автоматически продлена.

Списано с баланса: {amount}

Новая дата истечения: {new_expires_at}

{self._get_cabinet_button(language)} """, 'en': f"""

Auto-renewal Successful

Your subscription has been automatically renewed.

Charged from balance: {amount}

New expiration date: {new_expires_at}

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _autopay_failed_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for failed autopay notification.""" reason = context.get('reason', '') subjects = { 'ru': 'Ошибка автопродления', 'en': 'Auto-renewal Failed', 'zh': '自动续订失败', 'ua': 'Помилка автопродовження', } bodies = { 'ru': f"""

Ошибка автопродления

Не удалось автоматически продлить подписку.

{f'

Причина: {reason}

' if reason else ''}

Пожалуйста, пополните баланс и продлите подписку вручную.

{self._get_cabinet_button(language)} """, 'en': f"""

Auto-renewal Failed

Failed to automatically renew your subscription.

{f'

Reason: {reason}

' if reason else ''}

Please top up your balance and renew manually.

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _autopay_insufficient_funds_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for autopay insufficient funds notification.""" required = context.get('required_amount', '') balance = context.get('current_balance', '') subjects = { 'ru': 'Недостаточно средств для автопродления', 'en': 'Insufficient Funds for Auto-renewal', 'zh': '余额不足无法自动续订', 'ua': 'Недостатньо коштів для автопродовження', } bodies = { 'ru': f"""

Недостаточно средств

Недостаточно средств на балансе для автопродления подписки.

{f'

Требуется: {required}

' if required else ''} {f'

На балансе: {balance}

' if balance else ''}

Пополните баланс, чтобы подписка была продлена автоматически.

{self._get_cabinet_button(language)} """, 'en': f"""

Insufficient Funds

Insufficient balance for subscription auto-renewal.

{f'

Required: {required}

' if required else ''} {f'

Balance: {balance}

' if balance else ''}

Top up your balance for automatic renewal.

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } # ============================================================================ # Daily Subscription Templates # ============================================================================ def _daily_debit_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for daily subscription debit notification.""" amount = context.get('formatted_amount', f'{context.get("amount_rubles", 0):.2f} ₽') balance = context.get('formatted_balance', f'{context.get("new_balance_rubles", 0):.2f} ₽') subjects = { 'ru': f'Списание за подписку: {amount}', 'en': f'Subscription charge: {amount}', 'zh': f'订阅扣费: {amount}', 'ua': f'Списання за підписку: {amount}', } bodies = { 'ru': f"""

Ежедневное списание

С вашего баланса списано: {amount}

Остаток на балансе: {balance}

{self._get_cabinet_button(language)} """, 'en': f"""

Daily Charge

Charged from your balance: {amount}

Remaining balance: {balance}

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _daily_insufficient_funds_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for daily subscription insufficient funds.""" subjects = { 'ru': 'Недостаточно средств для продления', 'en': 'Insufficient Funds', 'zh': '余额不足', 'ua': 'Недостатньо коштів', } bodies = { 'ru': f"""

Недостаточно средств

На балансе недостаточно средств для продления подписки.

Подписка будет приостановлена.

Пополните баланс, чтобы продолжить использование сервиса.

{self._get_cabinet_button(language)} """, 'en': f"""

Insufficient Funds

Insufficient balance to continue subscription.

Your subscription will be suspended.

Please top up your balance to continue using the service.

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _traffic_reset_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for traffic reset notification.""" subjects = { 'ru': 'Трафик обновлён', 'en': 'Traffic Reset', 'zh': '流量已重置', 'ua': 'Трафік оновлено', } bodies = { 'ru': f"""

Трафик обновлён

Ваш трафик был сброшен. Вы можете продолжить использование VPN.

{self._get_cabinet_button(language)} """, 'en': f"""

Traffic Reset

Your traffic has been reset. You can continue using the VPN.

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } # ============================================================================ # Account Status Templates # ============================================================================ def _ban_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for ban notification.""" reason = context.get('reason', '') subjects = { 'ru': 'Аккаунт заблокирован', 'en': 'Account Suspended', 'zh': '账户已被封禁', 'ua': 'Обліковий запис заблоковано', } bodies = { 'ru': f"""

Аккаунт заблокирован

Ваш аккаунт был заблокирован.

{f'

Причина: {reason}

' if reason else ''}

Если вы считаете, что это ошибка, обратитесь в поддержку.

""", 'en': f"""

Account Suspended

Your account has been suspended.

{f'

Reason: {reason}

' if reason else ''}

If you believe this is an error, please contact support.

""", } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _unban_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for unban notification.""" subjects = { 'ru': 'Аккаунт разблокирован', 'en': 'Account Reactivated', 'zh': '账户已解封', 'ua': 'Обліковий запис розблоковано', } bodies = { 'ru': f"""

Аккаунт разблокирован

Ваш аккаунт был разблокирован.

Вы снова можете пользоваться сервисом.

{self._get_cabinet_button(language)} """, 'en': f"""

Account Reactivated

Your account has been reactivated.

You can use the service again.

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _warning_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for warning notification.""" message = context.get('message', '') subjects = { 'ru': 'Предупреждение', 'en': 'Warning', 'zh': '警告', 'ua': 'Попередження', } bodies = { 'ru': f"""

Предупреждение

{f'

{message}

' if message else '

Вы получили предупреждение от администрации.

'}
""", 'en': f"""

Warning

{f'

{message}

' if message else '

You have received a warning from the administration.

'}
""", } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } # ============================================================================ # Referral Templates # ============================================================================ def _referral_bonus_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for referral bonus notification.""" bonus = context.get('formatted_bonus', f'{context.get("bonus_rubles", 0):.2f} ₽') referral_name = context.get('referral_name', '') subjects = { 'ru': f'Реферальный бонус: +{bonus}', 'en': f'Referral bonus: +{bonus}', 'zh': f'推荐奖励: +{bonus}', 'ua': f'Реферальний бонус: +{bonus}', } bodies = { 'ru': f"""

Реферальный бонус!

Вы получили реферальный бонус: +{bonus}

{f'

Благодаря пользователю: {referral_name}

' if referral_name else ''}

Продолжайте приглашать друзей и зарабатывайте больше!

{self._get_cabinet_button(language)} """, 'en': f"""

Referral Bonus!

You received a referral bonus: +{bonus}

{f'

Thanks to: {referral_name}

' if referral_name else ''}

Keep inviting friends and earn more!

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } def _referral_registered_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for new referral registered notification.""" referral_name = context.get('referral_name', '') subjects = { 'ru': 'Новый реферал зарегистрирован', 'en': 'New Referral Registered', 'zh': '新推荐用户已注册', 'ua': 'Новий реферал зареєстрований', } bodies = { 'ru': f"""

Новый реферал!

По вашей ссылке зарегистрировался новый пользователь{f': {referral_name}' if referral_name else ''}.

Вы будете получать бонусы с его пополнений!

{self._get_cabinet_button(language)} """, 'en': f"""

New Referral!

A new user registered using your link{f': {referral_name}' if referral_name else ''}.

You will receive bonuses from their top-ups!

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } # ============================================================================ # Payment Templates # ============================================================================ def _payment_received_template(self, language: str, context: dict[str, Any]) -> dict[str, str]: """Template for payment received notification.""" amount = context.get('formatted_amount', f'{context.get("amount_rubles", 0):.2f} ₽') payment_method = context.get('payment_method', '') subjects = { 'ru': f'Платёж получен: {amount}', 'en': f'Payment received: {amount}', 'zh': f'收到付款: {amount}', 'ua': f'Платіж отримано: {amount}', } bodies = { 'ru': f"""

Платёж успешно обработан

Сумма: +{amount}

{f'

Способ оплаты: {payment_method}

' if payment_method else ''}

Спасибо за оплату!

{self._get_cabinet_button(language)} """, 'en': f"""

Payment Successfully Processed

Amount: +{amount}

{f'

Payment method: {payment_method}

' if payment_method else ''}

Thank you for your payment!

{self._get_cabinet_button(language)} """, } return { 'subject': subjects.get(language, subjects['en']), 'body_html': self._get_base_template(bodies.get(language, bodies['en'])), } # Singleton instance email_notification_templates = EmailNotificationTemplates()