package account
import (
"fmt"
"html/template"
"strings"
tmpltext "text/template"
"time"
)
// emailBrandColor is the single accent used in the confirmation email — a calm
// tile green, matching the "no riot of colours" brief.
const emailBrandColor = "#2f7d4f"
// confirmEmailView is the fully-localised data the confirmation email templates
// render. Every string is resolved before rendering, so the templates carry no
// localisation logic.
type confirmEmailView struct {
Brand string
Heading string
Intro string
Code string
Expiry string
CTALabel string
DeeplinkURL string
FooterIgnore string
LandingURL string
LandingLabel string
Preheader string
Locale string
Accent string
}
// emailCopy is the purpose- and locale-specific wording of a confirmation email.
type emailCopy struct {
Subject string
Preheader string
Heading string
Intro string
CTALabel string
FooterIgnore string
}
// confirmEmailCopy holds the wording per (purpose, locale). Unknown purposes fall
// back to the neutral link wording and unknown locales fall back to English.
var confirmEmailCopy = map[string]map[string]emailCopy{
purposeLogin: {
"en": {
Subject: "Your Erudit sign-in code",
Preheader: "Your sign-in code",
Heading: "Sign in to Erudit",
Intro: "Enter this code to sign in:",
CTALabel: "Sign in with one tap",
FooterIgnore: "If you didn't request this email, you can safely ignore it.",
},
"ru": {
Subject: "Код для входа в Эрудит",
Preheader: "Ваш код для входа",
Heading: "Вход в Эрудит",
Intro: "Введите этот код, чтобы войти в игру:",
CTALabel: "Войти одним нажатием",
FooterIgnore: "Если вы не запрашивали это письмо, просто проигнорируйте его.",
},
},
purposeLink: {
"en": {
Subject: "Your Erudit confirmation code",
Preheader: "Your confirmation code",
Heading: "Confirm your e-mail",
Intro: "Enter this code to confirm your address:",
CTALabel: "Confirm with one tap",
FooterIgnore: "If you didn't request this email, you can safely ignore it.",
},
"ru": {
Subject: "Код подтверждения Эрудит",
Preheader: "Ваш код подтверждения",
Heading: "Подтверждение e-mail",
Intro: "Введите этот код, чтобы подтвердить адрес:",
CTALabel: "Подтвердить одним нажатием",
FooterIgnore: "Если вы не запрашивали это письмо, просто проигнорируйте его.",
},
},
purposeChange: {
"en": {
Subject: "Confirm your new Erudit e-mail",
Preheader: "Confirm your new address",
Heading: "Confirm your new e-mail",
Intro: "Enter this code to switch your account to this address:",
CTALabel: "Confirm with one tap",
FooterIgnore: "If you didn't request this change, you can safely ignore it — your address stays the same.",
},
"ru": {
Subject: "Подтвердите новый e-mail в Эрудит",
Preheader: "Подтвердите новый адрес",
Heading: "Смена e-mail",
Intro: "Введите этот код, чтобы привязать аккаунт к новому адресу:",
CTALabel: "Подтвердить одним нажатием",
FooterIgnore: "Если вы не запрашивали смену, просто проигнорируйте письмо — адрес останется прежним.",
},
},
}
// emailBrand is the brand wordmark per locale.
var emailBrand = map[string]string{"en": "Erudit", "ru": "Эрудит"}
// emailExpiry formats the code-lifetime line per locale (abbreviated minutes to
// avoid plural agreement).
func emailExpiry(locale string, d time.Duration) string {
min := int(d / time.Minute)
if locale == "ru" {
return fmt.Sprintf("Код действует %d мин.", min)
}
return fmt.Sprintf("The code is valid for %d minutes.", min)
}
// normalizeLocale maps an account language to a supported email locale, defaulting
// to English.
func normalizeLocale(locale string) string {
if locale == "ru" {
return "ru"
}
return "en"
}
// renderConfirmationEmail builds the branded confirmation email for purpose in
// locale: a large readable code plus a one-tap deeplink button, with an
// ignore-notice footer and a landing link. Both a plain-text body and an HTML
// alternative are produced. deeplinkURL is the absolute /confirm link and
// landingURL the public landing origin.
func renderConfirmationEmail(purpose, code, deeplinkURL, landingURL, locale string) (Message, error) {
loc := normalizeLocale(locale)
byLocale, ok := confirmEmailCopy[purpose]
if !ok {
byLocale = confirmEmailCopy[purposeLink]
}
cp := byLocale[loc]
view := confirmEmailView{
Brand: emailBrand[loc],
Heading: cp.Heading,
Intro: cp.Intro,
Code: code,
Expiry: emailExpiry(loc, emailCodeTTL),
CTALabel: cp.CTALabel,
DeeplinkURL: deeplinkURL,
FooterIgnore: cp.FooterIgnore,
LandingURL: landingURL,
LandingLabel: emailBrand[loc],
Preheader: cp.Preheader,
Locale: loc,
Accent: emailBrandColor,
}
var html strings.Builder
if err := confirmEmailHTML.Execute(&html, view); err != nil {
return Message{}, fmt.Errorf("account: render confirmation email (html): %w", err)
}
var text strings.Builder
if err := confirmEmailText.Execute(&text, view); err != nil {
return Message{}, fmt.Errorf("account: render confirmation email (text): %w", err)
}
return Message{Subject: cp.Subject, Text: text.String(), HTML: html.String()}, nil
}
// confirmEmailHTML is a compact, image-free, mobile-friendly HTML email. Layout is
// table-based for broad mail-client compatibility and all styling is inlined
// because clients strip