TL;DR: 多语言邮件模板需要精细的设计——本地化内容感知、模板继承、动态主题行以及健壮的兜底链路。本文覆盖架构思路与工程实现。相关模式参考 tanstackship.com。
引言
邮件是 SaaS 产品的核心沟通渠道,发送错误语言的邮件会严重损害用户信任。一套完善的多语言邮件系统需要处理多语言翻译、 locale 特定格式以及 10+ 语言环境下的内容变体,同时避免模板重复。完整的 i18n 战略可参考我们的 i18n for SaaS: Architecture Patterns Compared。
邮件模板架构
| 组件 | 职责 | 示例 |
|---|---|---|
| Template engine(模板引擎) | 变量渲染 HTML | Handlebars、MJML、React Email |
| Translation service(翻译服务) | Locale 感知的内容 | Paraglide.js JSON 文件 |
| Formatting service(格式化服务) | Locale 感知的数字/日期 | Intl API |
| Image service(图片服务) | Locale 特定的图片 | CDN + locale 路径 |
| Fallback chain(兜底链路) | 缺失翻译处理 | target → en → key |
模板结构
emails/
├── en/
│ ├── welcome.html # 英文版本
│ ├── welcome.subject # 仅主题行
│ ├── invoice.html
│ └── invoice.subject
├── de/
│ ├── welcome.html
│ ├── welcome.subject
│ ├── invoice.html
│ └── invoice.subject
└── shared/
├── header.html # 共享组件
├── footer.html
└── styles.css
使用 React Email 实现
// 带有 i18n 支持的 React Email 组件
import * as m from '~/paraglide/messages.js'
import {
Body, Container, Head, Heading, Html,
Preview, Section, Text, Button,
} from '@react-email/components'
interface WelcomeEmailProps {
userName: string
locale: string
dashboardUrl: string
}
export function WelcomeEmail({ userName, locale, dashboardUrl }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Preview>{m.email_welcome_preview({ name: userName })}</Preview>
<Body>
<Container>
<Heading>{m.email_welcome_title({ name: userName })}</Heading>
<Text>{m.email_welcome_body()}</Text>
<Section>
<Button href={dashboardUrl}>
{m.email_welcome_cta()}
</Button>
</Section>
<Text style={{ fontSize: '12px', color: '#666' }}>
{m.email_welcome_footer()}
</Text>
</Container>
</Body>
</Html>
)
}
动态主题行
// 每个 locale 的主题行模板,支持变量
const subjectTemplates = {
en: 'Welcome to TanStack Ship, {name}!',
de: 'Willkommen bei TanStack Ship, {name}!',
zh: '{name},欢迎使用 TanStack Ship!',
ja: '{name}さん、TanStack Shipへようこそ!',
ar: 'مرحبًا {name} في TanStack Ship!',
}
function getSubject(template: string, locale: string, vars: Record<string, string>) {
let subject = subjectTemplates[locale] || subjectTemplates['en']
Object.entries(vars).forEach(([key, value]) => {
subject = subject.replace(`{${key}}`, value)
})
return subject
}
发送与兜底逻辑
// 带 locale 兜底的邮件发送
async function sendEmail({
to,
locale,
template,
data,
}: {
to: string
locale: string
template: string
data: Record<string, any>
}) {
const effectiveLocale = SUPPORTED_LOCALES.includes(locale) ? locale : 'en'
// 按兜底链路加载模板
const html = await loadTemplate(template, effectiveLocale, data)
const subject = await loadSubject(template, effectiveLocale, data)
await sendEmailClient({
to,
subject,
html,
headers: {
// 追踪邮件语言以供分析用
'X-Email-Locale': effectiveLocale,
},
})
}
多语言邮件测试
| 测试用例 | 方法 | 检查项 |
|---|---|---|
| 所有 locale 均正常渲染 | 自动化渲染测试 | 无缺失 key,无报错 |
| 主题行字符长度 | 字符计数 | 阿拉伯语/中文主题行可能更短 |
| RTL 渲染效果 | 视觉检查 | 阿拉伯语邮件从右到左阅读 |
| 日期/数字格式 | 快照测试 | Locale 正确的格式 |
| 兜底行为 | 用不支持的 locale 测试 | 优雅降级到 en |
总结
使用统一的模板系统配合 locale 特定的内容文件来管理多语言邮件。React Email 组件结合 Paraglide.js 可提供类型安全的翻译。当某个翻译缺失时,始终渲染一个兜底 locale。邮件模板需要在 CI 流水线中对所有支持的 locale 进行全量测试。
邮件中的 locale 感知格式化可参考 Localizing Date, Time, Number, and Currency Formats。我们的 SaaS Email Deliverability Guide 涵盖 SPF、DKIM 和 DMARC 配置。构建 locale 特定的多语言着陆页策略,请参阅 Building a Language-Specific Landing Page Strategy。