From a095117061ae6fa726dcf13cb72f7ea22f7713bc Mon Sep 17 00:00:00 2001 From: Darren Yu Date: Tue, 26 Aug 2025 11:02:38 +0800 Subject: [PATCH] feat(email): support magic variables in email title, add init email template for multiple languages (#2814) * feat(email): add init email template for multiple languages * Update setting.go * Update setting.go * feat(email): support magic variables in email title --- inventory/setting.go | 174 +++++++++++++++++++++++++++++++++++++++++- pkg/email/template.go | 40 +++++++--- 2 files changed, 201 insertions(+), 13 deletions(-) diff --git a/inventory/setting.go b/inventory/setting.go index fa09f59..d769ed9 100644 --- a/inventory/setting.go +++ b/inventory/setting.go @@ -340,8 +340,137 @@ var ( Max: 5, }, } + + defaultActiveMailBody = `
` + defaultResetMailBody = `
` ) +type MailTemplateContent struct { + Language string + EmailIsAutoSend string // Translation of `此邮件由系统自动发送。` + + ActiveTitle string // Translation of `激活你的账号` + ActiveDes string // Translation of `请点击下方按钮确认你的电子邮箱并完成账号注册,此链接有效期为 24 小时。` + ActiveButton string // Translation of `确认激活` + + ResetTitle string // Translation of `重设密码` + ResetDes string // Translation of `请点击下方按钮重设你的密码,此链接有效期为 1 小时。` + ResetButton string // Translation of `重设密码` +} + +var mailTemplateContents = []MailTemplateContent{ + { + Language: "en-US", + EmailIsAutoSend: "This email is sent automatically.", + ActiveTitle: "Confirm your account", + ActiveDes: "Please click the button below to confirm your email address and finish setting up your account. This link is valid for 24 hours.", + ActiveButton: "Confirm", + ResetTitle: "Reset your password", + ResetDes: "Please click the button below to reset your password. This link is valid for 1 hour.", + ResetButton: "Reset", + }, + { + Language: "zh-CN", + EmailIsAutoSend: "此邮件由系统自动发送。", + ActiveTitle: "激活你的账号", + ActiveDes: "请点击下方按钮确认你的电子邮箱并完成账号注册,此链接有效期为 24 小时。", + ActiveButton: "确认激活", + ResetTitle: "重设密码", + ResetDes: "请点击下方按钮重设你的密码,此链接有效期为 1 小时。", + ResetButton: "重设密码", + }, + { + Language: "zh-TW", + EmailIsAutoSend: "此郵件由系統自動發送。", + ActiveTitle: "激活你的帳號", + ActiveDes: "請點擊下方按鈕確認你的電子郵箱並完成帳號註冊,此連結有效期為 24 小時。", + ActiveButton: "確認激活", + ResetTitle: "重設密碼", + ResetDes: "請點擊下方按鈕重設你的密碼,此連結有效期為 1 小時。", + ResetButton: "重設密碼", + }, + { + Language: "de-DE", + EmailIsAutoSend: "Diese E-Mail wird automatisch vom System gesendet.", + ActiveTitle: "Bestätigen Sie Ihr Konto", + ActiveDes: "Bitte klicken Sie auf die Schaltfläche unten, um Ihre E-Mail-Adresse zu bestätigen und Ihr Konto einzurichten. Dieser Link ist 24 Stunden lang gültig.", + ActiveButton: "Bestätigen", + ResetTitle: "Passwort zurücksetzen", + ResetDes: "Bitte klicken Sie auf die Schaltfläche unten, um Ihr Passwort zurückzusetzen. Dieser Link ist 1 Stunde lang gültig.", + ResetButton: "Passwort zurücksetzen", + }, + { + Language: "es-ES", + EmailIsAutoSend: "Este correo electrónico se envía automáticamente.", + ActiveTitle: "Confirma tu cuenta", + ActiveDes: "Por favor, haz clic en el botón de abajo para confirmar tu dirección de correo electrónico y completar la configuración de tu cuenta. Este enlace es válido por 24 horas.", + ActiveButton: "Confirmar", + ResetTitle: "Restablecer tu contraseña", + ResetDes: "Por favor, haz clic en el botón de abajo para restablecer tu contraseña. Este enlace es válido por 1 hora.", + ResetButton: "Restablecer", + }, + { + Language: "fr-FR", + EmailIsAutoSend: "Cet e-mail est envoyé automatiquement.", + ActiveTitle: "Confirmer votre compte", + ActiveDes: "Veuillez cliquer sur le bouton ci-dessous pour confirmer votre adresse e-mail et terminer la configuration de votre compte. Ce lien est valable 24 heures.", + ActiveButton: "Confirmer", + ResetTitle: "Réinitialiser votre mot de passe", + ResetDes: "Veuillez cliquer sur le bouton ci-dessous pour réinitialiser votre mot de passe. Ce lien est valable 1 heure.", + ResetButton: "Réinitialiser", + }, + { + Language: "it-IT", + EmailIsAutoSend: "Questa email è inviata automaticamente.", + ActiveTitle: "Conferma il tuo account", + ActiveDes: "Per favore, clicca sul pulsante qui sotto per confermare il tuo indirizzo email e completare la configurazione del tuo account. Questo link è valido per 24 ore.", + ActiveButton: "Conferma", + ResetTitle: "Reimposta la tua password", + ResetDes: "Per favore, clicca sul pulsante qui sotto per reimpostare la tua password. Questo link è valido per 1 ora.", + ResetButton: "Reimposta", + }, + { + Language: "ja-JP", + EmailIsAutoSend: "このメールはシステムによって自動的に送信されました。", + ActiveTitle: "アカウントを確認する", + ActiveDes: "アカウントの設定を完了するために、以下のボタンをクリックしてメールアドレスを確認してください。このリンクは24時間有効です。", + ActiveButton: "確認する", + ResetTitle: "パスワードをリセットする", + ResetDes: "以下のボタンをクリックしてパスワードをリセットしてください。このリンクは1時間有効です。", + ResetButton: "リセットする", + }, + { + Language: "ko-KR", + EmailIsAutoSend: "이 이메일은 시스템에 의해 자동으로 전송됩니다.", + ActiveTitle: "계정 확인", + ActiveDes: "아래 버튼을 클릭하여 이메일 주소를 확인하고 계정을 설정하세요. 이 링크는 24시간 동안 유효합니다.", + ActiveButton: "확인", + ResetTitle: "비밀번호 재설정", + ResetDes: "아래 버튼을 클릭하여 비밀번호를 재설정하세요. 이 링크는 1시간 동안 유효합니다.", + ResetButton: "비밀번호 재설정", + }, + { + Language: "pt-BR", + EmailIsAutoSend: "Este e-mail é enviado automaticamente.", + ActiveTitle: "Confirme sua conta", + ActiveDes: "Por favor, clique no botão abaixo para confirmar seu endereço de e-mail e concluir a configuração da sua conta. Este link é válido por 24 horas.", + ActiveButton: "Confirmar", + ResetTitle: "Redefinir sua senha", + ResetDes: "Por favor, clique no botão abaixo para redefinir sua senha. Este link é válido por 1 hora.", + ResetButton: "Redefinir", + }, + { + Language: "ru-RU", + EmailIsAutoSend: "Это письмо отправлено автоматически.", + ActiveTitle: "Подтвердите вашу учетную запись", + ActiveDes: "Пожалуйста, нажмите кнопку ниже, чтобы подтвердить ваш адрес электронной почты и завершить настройку вашей учетной записи. Эта ссылка действительна в течение 24 часов.", + ActiveButton: "Подтвердить", + ResetTitle: "Сбросить ваш пароль", + ResetDes: "Пожалуйста, нажмите кнопку ниже, чтобы сбросить ваш пароль. Эта ссылка действительна в течение 1 часа.", + ResetButton: "Сбросить пароль", + }, +} + var DefaultSettings = map[string]string{ "siteURL": `http://localhost:5212`, "siteName": `Cloudreve`, @@ -448,8 +577,6 @@ var DefaultSettings = map[string]string{ "public_resource_maxage": "86400", "viewer_session_timeout": "36000", "hash_id_salt": util.RandStringRunes(64), - "mail_activation_template": `[{"language":"en-US","title":"Activate your account","body":"
                                                           
"},{"language":"zh-CN","title":"激活你的账号","body":"
                                                           
"}]`, - "mail_reset_template": `[{"language":"en-US","title":"Reset your password","body":"
                                                           
"},{"language":"zh-CN","title":"重设密码","body":"
                                                           
"}]`, "access_token_ttl": "3600", "refresh_token_ttl": "1209600", // 2 weeks "use_cursor_pagination": "1", @@ -535,7 +662,6 @@ func init() { if err != nil { panic(err) } - DefaultSettings["file_viewers"] = string(viewers) customProps, err := json.Marshal(defaultFileProps) @@ -543,4 +669,44 @@ func init() { panic(err) } DefaultSettings["custom_props"] = string(customProps) -} + + activeMails := []map[string]string{} + for _, langContents := range mailTemplateContents { + activeMails = append(activeMails, map[string]string{ + "language": langContents.Language, + "title": "[{{ .CommonContext.SiteBasic.Name }}] " + langContents.ActiveTitle, + "body": util.Replace(map[string]string{ + "[[ .Language ]]": langContents.Language, + "[[ .ActiveTitle ]]": langContents.ActiveTitle, + "[[ .ActiveDes ]]": langContents.ActiveDes, + "[[ .ActiveButton ]]": langContents.ActiveButton, + "[[ .EmailIsAutoSend ]]": langContents.EmailIsAutoSend, + }, defaultActiveMailBody), + }) + } + mailActivationTemplates, err := json.Marshal(activeMails) + if err != nil { + panic(err) + } + DefaultSettings["mail_activation_template"] = string(mailActivationTemplates) + + resetMails := []map[string]string{} + for _, langContents := range mailTemplateContents { + resetMails = append(resetMails, map[string]string{ + "language": langContents.Language, + "title": "[{{ .CommonContext.SiteBasic.Name }}] " + langContents.ResetTitle, + "body": util.Replace(map[string]string{ + "[[ .Language ]]": langContents.Language, + "[[ .ResetTitle ]]": langContents.ResetTitle, + "[[ .ResetDes ]]": langContents.ResetDes, + "[[ .ResetButton ]]": langContents.ResetButton, + "[[ .EmailIsAutoSend ]]": langContents.EmailIsAutoSend, + }, defaultResetMailBody), + }) + } + mailResetTemplates, err := json.Marshal(resetMails) + if err != nil { + panic(err) + } + DefaultSettings["mail_reset_template"] = string(mailResetTemplates) +} \ No newline at end of file diff --git a/pkg/email/template.go b/pkg/email/template.go index d732eec..213c092 100644 --- a/pkg/email/template.go +++ b/pkg/email/template.go @@ -38,18 +38,29 @@ func NewResetEmail(ctx context.Context, settings setting.Provider, user *ent.Use Url: url, } - tmpl, err := template.New("reset").Parse(selected.Body) + tmplTitle, err := template.New("resetTitle").Parse(selected.Title) + if err != nil { + return "", "", fmt.Errorf("failed to parse email title: %w", err) + } + + var resTitle strings.Builder + err = tmplTitle.Execute(&resTitle, resetCtx) + if err != nil { + return "", "", fmt.Errorf("failed to execute email title: %w", err) + } + + tmplBody, err := template.New("resetBody").Parse(selected.Body) if err != nil { return "", "", fmt.Errorf("failed to parse email template: %w", err) } - var res strings.Builder - err = tmpl.Execute(&res, resetCtx) + var resBody strings.Builder + err = tmplBody.Execute(&resBody, resetCtx) if err != nil { return "", "", fmt.Errorf("failed to execute email template: %w", err) } - return fmt.Sprintf("[%s] %s", resetCtx.SiteBasic.Name, selected.Title), res.String(), nil + return resTitle.String(), resBody.String(), nil } // ActivationContext used for variables in activation email @@ -73,18 +84,29 @@ func NewActivationEmail(ctx context.Context, settings setting.Provider, user *en Url: url, } - tmpl, err := template.New("activation").Parse(selected.Body) + tmplTitle, err := template.New("activationTitle").Parse(selected.Title) + if err != nil { + return "", "", fmt.Errorf("failed to parse email title: %w", err) + } + + var resTitle strings.Builder + err = tmplTitle.Execute(&resTitle, activationCtx) + if err != nil { + return "", "", fmt.Errorf("failed to execute email title: %w", err) + } + + tmplBody, err := template.New("activationBody").Parse(selected.Body) if err != nil { return "", "", fmt.Errorf("failed to parse email template: %w", err) } - var res strings.Builder - err = tmpl.Execute(&res, activationCtx) + var resBody strings.Builder + err = tmplBody.Execute(&resBody, activationCtx) if err != nil { return "", "", fmt.Errorf("failed to execute email template: %w", err) } - return fmt.Sprintf("[%s] %s", activationCtx.SiteBasic.Name, selected.Title), res.String(), nil + return resTitle.String(), resBody.String(), nil } func commonContext(ctx context.Context, settings setting.Provider) *CommonContext { @@ -122,4 +144,4 @@ func selectTemplate(templates []setting.EmailTemplate, u *ent.User) setting.Emai } return selected -} +} \ No newline at end of file