| 
                        123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 | 
                        - """
 - Email Internationalization Module
 - 
 - This module provides a centralized, elegant way to handle email internationalization
 - in Dify. It follows Domain-Driven Design principles with proper type hints and
 - eliminates the need for repetitive language switching logic.
 - """
 - 
 - from dataclasses import dataclass
 - from enum import StrEnum, auto
 - from typing import Any, Optional, Protocol
 - 
 - from flask import render_template
 - from pydantic import BaseModel, Field
 - 
 - from extensions.ext_mail import mail
 - from services.feature_service import BrandingModel, FeatureService
 - 
 - 
 - class EmailType(StrEnum):
 -     """Enumeration of supported email types."""
 - 
 -     RESET_PASSWORD = auto()
 -     RESET_PASSWORD_WHEN_ACCOUNT_NOT_EXIST = auto()
 -     INVITE_MEMBER = auto()
 -     EMAIL_CODE_LOGIN = auto()
 -     CHANGE_EMAIL_OLD = auto()
 -     CHANGE_EMAIL_NEW = auto()
 -     CHANGE_EMAIL_COMPLETED = auto()
 -     OWNER_TRANSFER_CONFIRM = auto()
 -     OWNER_TRANSFER_OLD_NOTIFY = auto()
 -     OWNER_TRANSFER_NEW_NOTIFY = auto()
 -     ACCOUNT_DELETION_SUCCESS = auto()
 -     ACCOUNT_DELETION_VERIFICATION = auto()
 -     ENTERPRISE_CUSTOM = auto()
 -     QUEUE_MONITOR_ALERT = auto()
 -     DOCUMENT_CLEAN_NOTIFY = auto()
 -     EMAIL_REGISTER = auto()
 -     EMAIL_REGISTER_WHEN_ACCOUNT_EXIST = auto()
 -     RESET_PASSWORD_WHEN_ACCOUNT_NOT_EXIST_NO_REGISTER = auto()
 - 
 - 
 - class EmailLanguage(StrEnum):
 -     """Supported email languages with fallback handling."""
 - 
 -     EN_US = "en-US"
 -     ZH_HANS = "zh-Hans"
 - 
 -     @classmethod
 -     def from_language_code(cls, language_code: str) -> "EmailLanguage":
 -         """Convert a language code to EmailLanguage with fallback to English."""
 -         if language_code == "zh-Hans":
 -             return cls.ZH_HANS
 -         return cls.EN_US
 - 
 - 
 - @dataclass(frozen=True)
 - class EmailTemplate:
 -     """Immutable value object representing an email template configuration."""
 - 
 -     subject: str
 -     template_path: str
 -     branded_template_path: str
 - 
 - 
 - @dataclass(frozen=True)
 - class EmailContent:
 -     """Immutable value object containing rendered email content."""
 - 
 -     subject: str
 -     html_content: str
 -     template_context: dict[str, Any]
 - 
 - 
 - class EmailI18nConfig(BaseModel):
 -     """Configuration for email internationalization."""
 - 
 -     model_config = {"frozen": True, "extra": "forbid"}
 - 
 -     templates: dict[EmailType, dict[EmailLanguage, EmailTemplate]] = Field(
 -         default_factory=dict, description="Mapping of email types to language-specific templates"
 -     )
 - 
 -     def get_template(self, email_type: EmailType, language: EmailLanguage) -> EmailTemplate:
 -         """Get template configuration for specific email type and language."""
 -         type_templates = self.templates.get(email_type)
 -         if not type_templates:
 -             raise ValueError(f"No templates configured for email type: {email_type}")
 - 
 -         template = type_templates.get(language)
 -         if not template:
 -             # Fallback to English if specific language not found
 -             template = type_templates.get(EmailLanguage.EN_US)
 -             if not template:
 -                 raise ValueError(f"No template found for {email_type} in {language} or English")
 - 
 -         return template
 - 
 - 
 - class EmailRenderer(Protocol):
 -     """Protocol for email template renderers."""
 - 
 -     def render_template(self, template_path: str, **context: Any) -> str:
 -         """Render email template with given context."""
 -         ...
 - 
 - 
 - class FlaskEmailRenderer:
 -     """Flask-based email template renderer."""
 - 
 -     def render_template(self, template_path: str, **context: Any) -> str:
 -         """Render email template using Flask's render_template."""
 -         return render_template(template_path, **context)
 - 
 - 
 - class BrandingService(Protocol):
 -     """Protocol for branding service abstraction."""
 - 
 -     def get_branding_config(self) -> BrandingModel:
 -         """Get current branding configuration."""
 -         ...
 - 
 - 
 - class FeatureBrandingService:
 -     """Feature service based branding implementation."""
 - 
 -     def get_branding_config(self) -> BrandingModel:
 -         """Get branding configuration from feature service."""
 -         return FeatureService.get_system_features().branding
 - 
 - 
 - class EmailSender(Protocol):
 -     """Protocol for email sending abstraction."""
 - 
 -     def send_email(self, to: str, subject: str, html_content: str):
 -         """Send email with given parameters."""
 -         ...
 - 
 - 
 - class FlaskMailSender:
 -     """Flask-Mail based email sender."""
 - 
 -     def send_email(self, to: str, subject: str, html_content: str):
 -         """Send email using Flask-Mail."""
 -         if mail.is_inited():
 -             mail.send(to=to, subject=subject, html=html_content)
 - 
 - 
 - class EmailI18nService:
 -     """
 -     Main service for internationalized email handling.
 - 
 -     This service provides a clean API for sending internationalized emails
 -     with proper branding support and template management.
 -     """
 - 
 -     def __init__(
 -         self,
 -         config: EmailI18nConfig,
 -         renderer: EmailRenderer,
 -         branding_service: BrandingService,
 -         sender: EmailSender,
 -     ):
 -         self._config = config
 -         self._renderer = renderer
 -         self._branding_service = branding_service
 -         self._sender = sender
 - 
 -     def send_email(
 -         self,
 -         email_type: EmailType,
 -         language_code: str,
 -         to: str,
 -         template_context: Optional[dict[str, Any]] = None,
 -     ):
 -         """
 -         Send internationalized email with branding support.
 - 
 -         Args:
 -             email_type: Type of email to send
 -             language_code: Target language code
 -             to: Recipient email address
 -             template_context: Additional context for template rendering
 -         """
 -         if template_context is None:
 -             template_context = {}
 - 
 -         language = EmailLanguage.from_language_code(language_code)
 -         email_content = self._render_email_content(email_type, language, template_context)
 - 
 -         self._sender.send_email(to=to, subject=email_content.subject, html_content=email_content.html_content)
 - 
 -     def send_change_email(
 -         self,
 -         language_code: str,
 -         to: str,
 -         code: str,
 -         phase: str,
 -     ):
 -         """
 -         Send change email notification with phase-specific handling.
 - 
 -         Args:
 -             language_code: Target language code
 -             to: Recipient email address
 -             code: Verification code
 -             phase: Either 'old_email' or 'new_email'
 -         """
 -         if phase == "old_email":
 -             email_type = EmailType.CHANGE_EMAIL_OLD
 -         elif phase == "new_email":
 -             email_type = EmailType.CHANGE_EMAIL_NEW
 -         else:
 -             raise ValueError(f"Invalid phase: {phase}. Must be 'old_email' or 'new_email'")
 - 
 -         self.send_email(
 -             email_type=email_type,
 -             language_code=language_code,
 -             to=to,
 -             template_context={
 -                 "to": to,
 -                 "code": code,
 -             },
 -         )
 - 
 -     def send_raw_email(
 -         self,
 -         to: str | list[str],
 -         subject: str,
 -         html_content: str,
 -     ):
 -         """
 -         Send a raw email directly without template processing.
 - 
 -         This method is provided for backward compatibility with legacy email
 -         sending that uses pre-rendered HTML content (e.g., enterprise emails
 -         with custom templates).
 - 
 -         Args:
 -             to: Recipient email address(es)
 -             subject: Email subject
 -             html_content: Pre-rendered HTML content
 -         """
 -         if isinstance(to, list):
 -             for recipient in to:
 -                 self._sender.send_email(to=recipient, subject=subject, html_content=html_content)
 -         else:
 -             self._sender.send_email(to=to, subject=subject, html_content=html_content)
 - 
 -     def _render_email_content(
 -         self,
 -         email_type: EmailType,
 -         language: EmailLanguage,
 -         template_context: dict[str, Any],
 -     ) -> EmailContent:
 -         """Render email content with branding and internationalization."""
 -         template_config = self._config.get_template(email_type, language)
 -         branding = self._branding_service.get_branding_config()
 - 
 -         # Determine template path based on branding
 -         template_path = template_config.branded_template_path if branding.enabled else template_config.template_path
 - 
 -         # Prepare template context with branding information
 -         full_context = {
 -             **template_context,
 -             "branding_enabled": branding.enabled,
 -             "application_title": branding.application_title if branding.enabled else "Dify",
 -         }
 - 
 -         # Render template
 -         html_content = self._renderer.render_template(template_path, **full_context)
 - 
 -         # Apply templating to subject with all context variables
 -         subject = template_config.subject
 -         try:
 -             subject = subject.format(**full_context)
 -         except KeyError:
 -             # If template variables are missing, fall back to basic formatting
 -             if branding.enabled and "{application_title}" in subject:
 -                 subject = subject.format(application_title=branding.application_title)
 - 
 -         return EmailContent(
 -             subject=subject,
 -             html_content=html_content,
 -             template_context=full_context,
 -         )
 - 
 - 
 - def create_default_email_config() -> EmailI18nConfig:
 -     """Create default email i18n configuration with all supported templates."""
 -     templates: dict[EmailType, dict[EmailLanguage, EmailTemplate]] = {
 -         EmailType.RESET_PASSWORD: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Set Your {application_title} Password",
 -                 template_path="reset_password_mail_template_en-US.html",
 -                 branded_template_path="without-brand/reset_password_mail_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="设置您的 {application_title} 密码",
 -                 template_path="reset_password_mail_template_zh-CN.html",
 -                 branded_template_path="without-brand/reset_password_mail_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.INVITE_MEMBER: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Join {application_title} Workspace Now",
 -                 template_path="invite_member_mail_template_en-US.html",
 -                 branded_template_path="without-brand/invite_member_mail_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="立即加入 {application_title} 工作空间",
 -                 template_path="invite_member_mail_template_zh-CN.html",
 -                 branded_template_path="without-brand/invite_member_mail_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.EMAIL_CODE_LOGIN: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="{application_title} Login Code",
 -                 template_path="email_code_login_mail_template_en-US.html",
 -                 branded_template_path="without-brand/email_code_login_mail_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="{application_title} 登录验证码",
 -                 template_path="email_code_login_mail_template_zh-CN.html",
 -                 branded_template_path="without-brand/email_code_login_mail_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.CHANGE_EMAIL_OLD: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Check your current email",
 -                 template_path="change_mail_confirm_old_template_en-US.html",
 -                 branded_template_path="without-brand/change_mail_confirm_old_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="检测您现在的邮箱",
 -                 template_path="change_mail_confirm_old_template_zh-CN.html",
 -                 branded_template_path="without-brand/change_mail_confirm_old_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.CHANGE_EMAIL_NEW: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Confirm your new email address",
 -                 template_path="change_mail_confirm_new_template_en-US.html",
 -                 branded_template_path="without-brand/change_mail_confirm_new_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="确认您的邮箱地址变更",
 -                 template_path="change_mail_confirm_new_template_zh-CN.html",
 -                 branded_template_path="without-brand/change_mail_confirm_new_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.CHANGE_EMAIL_COMPLETED: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Your login email has been changed",
 -                 template_path="change_mail_completed_template_en-US.html",
 -                 branded_template_path="without-brand/change_mail_completed_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="您的登录邮箱已更改",
 -                 template_path="change_mail_completed_template_zh-CN.html",
 -                 branded_template_path="without-brand/change_mail_completed_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.OWNER_TRANSFER_CONFIRM: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Verify Your Request to Transfer Workspace Ownership",
 -                 template_path="transfer_workspace_owner_confirm_template_en-US.html",
 -                 branded_template_path="without-brand/transfer_workspace_owner_confirm_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="验证您转移工作空间所有权的请求",
 -                 template_path="transfer_workspace_owner_confirm_template_zh-CN.html",
 -                 branded_template_path="without-brand/transfer_workspace_owner_confirm_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.OWNER_TRANSFER_OLD_NOTIFY: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Workspace ownership has been transferred",
 -                 template_path="transfer_workspace_old_owner_notify_template_en-US.html",
 -                 branded_template_path="without-brand/transfer_workspace_old_owner_notify_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="工作区所有权已转移",
 -                 template_path="transfer_workspace_old_owner_notify_template_zh-CN.html",
 -                 branded_template_path="without-brand/transfer_workspace_old_owner_notify_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.OWNER_TRANSFER_NEW_NOTIFY: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="You are now the owner of {WorkspaceName}",
 -                 template_path="transfer_workspace_new_owner_notify_template_en-US.html",
 -                 branded_template_path="without-brand/transfer_workspace_new_owner_notify_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="您现在是 {WorkspaceName} 的所有者",
 -                 template_path="transfer_workspace_new_owner_notify_template_zh-CN.html",
 -                 branded_template_path="without-brand/transfer_workspace_new_owner_notify_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.ACCOUNT_DELETION_SUCCESS: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Your Dify.AI Account Has Been Successfully Deleted",
 -                 template_path="delete_account_success_template_en-US.html",
 -                 branded_template_path="delete_account_success_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="您的 Dify.AI 账户已成功删除",
 -                 template_path="delete_account_success_template_zh-CN.html",
 -                 branded_template_path="delete_account_success_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.ACCOUNT_DELETION_VERIFICATION: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Dify.AI Account Deletion and Verification",
 -                 template_path="delete_account_code_email_template_en-US.html",
 -                 branded_template_path="delete_account_code_email_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="Dify.AI 账户删除和验证",
 -                 template_path="delete_account_code_email_template_zh-CN.html",
 -                 branded_template_path="delete_account_code_email_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.QUEUE_MONITOR_ALERT: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Alert: Dataset Queue pending tasks exceeded the limit",
 -                 template_path="queue_monitor_alert_email_template_en-US.html",
 -                 branded_template_path="queue_monitor_alert_email_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="警报:数据集队列待处理任务超过限制",
 -                 template_path="queue_monitor_alert_email_template_zh-CN.html",
 -                 branded_template_path="queue_monitor_alert_email_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.DOCUMENT_CLEAN_NOTIFY: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Dify Knowledge base auto disable notification",
 -                 template_path="clean_document_job_mail_template-US.html",
 -                 branded_template_path="clean_document_job_mail_template-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="Dify 知识库自动禁用通知",
 -                 template_path="clean_document_job_mail_template_zh-CN.html",
 -                 branded_template_path="clean_document_job_mail_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.EMAIL_REGISTER: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Register Your {application_title} Account",
 -                 template_path="register_email_template_en-US.html",
 -                 branded_template_path="without-brand/register_email_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="注册您的 {application_title} 账户",
 -                 template_path="register_email_template_zh-CN.html",
 -                 branded_template_path="without-brand/register_email_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.EMAIL_REGISTER_WHEN_ACCOUNT_EXIST: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Register Your {application_title} Account",
 -                 template_path="register_email_when_account_exist_template_en-US.html",
 -                 branded_template_path="without-brand/register_email_when_account_exist_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="注册您的 {application_title} 账户",
 -                 template_path="register_email_when_account_exist_template_zh-CN.html",
 -                 branded_template_path="without-brand/register_email_when_account_exist_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.RESET_PASSWORD_WHEN_ACCOUNT_NOT_EXIST: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Reset Your {application_title} Password",
 -                 template_path="reset_password_mail_when_account_not_exist_template_en-US.html",
 -                 branded_template_path="without-brand/reset_password_mail_when_account_not_exist_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="重置您的 {application_title} 密码",
 -                 template_path="reset_password_mail_when_account_not_exist_template_zh-CN.html",
 -                 branded_template_path="without-brand/reset_password_mail_when_account_not_exist_template_zh-CN.html",
 -             ),
 -         },
 -         EmailType.RESET_PASSWORD_WHEN_ACCOUNT_NOT_EXIST_NO_REGISTER: {
 -             EmailLanguage.EN_US: EmailTemplate(
 -                 subject="Reset Your {application_title} Password",
 -                 template_path="reset_password_mail_when_account_not_exist_no_register_template_en-US.html",
 -                 branded_template_path="without-brand/reset_password_mail_when_account_not_exist_no_register_template_en-US.html",
 -             ),
 -             EmailLanguage.ZH_HANS: EmailTemplate(
 -                 subject="重置您的 {application_title} 密码",
 -                 template_path="reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html",
 -                 branded_template_path="without-brand/reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html",
 -             ),
 -         },
 -     }
 - 
 -     return EmailI18nConfig(templates=templates)
 - 
 - 
 - # Singleton instance for application-wide use
 - def get_default_email_i18n_service() -> EmailI18nService:
 -     """Get configured email i18n service with default dependencies."""
 -     config = create_default_email_config()
 -     renderer = FlaskEmailRenderer()
 -     branding_service = FeatureBrandingService()
 -     sender = FlaskMailSender()
 - 
 -     return EmailI18nService(
 -         config=config,
 -         renderer=renderer,
 -         branding_service=branding_service,
 -         sender=sender,
 -     )
 - 
 - 
 - # Global instance
 - _email_i18n_service: Optional[EmailI18nService] = None
 - 
 - 
 - def get_email_i18n_service() -> EmailI18nService:
 -     """Get global email i18n service instance."""
 -     global _email_i18n_service
 -     if _email_i18n_service is None:
 -         _email_i18n_service = get_default_email_i18n_service()
 -     return _email_i18n_service
 
 
  |