You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Express应用中用i18n+Handlebars本地化邮件模板的并发问题求解

解决Express中Handlebars模板邮件的本地化并发问题

我之前也碰到过这个一模一样的问题——全局Handlebars助手和请求级i18n的冲突简直是噩梦,尤其是并发请求多的时候,locale串得一塌糊涂。下面是我摸索出来的可行方案,亲测稳定:

核心思路

不要用全局注册的Handlebars翻译助手,而是将当前请求上下文的__翻译函数直接传入邮件模板的渲染上下文。这样每个邮件渲染都使用独立的、绑定到当前请求locale的翻译函数,完全避免全局状态的并发污染。

步骤1:正确配置i18n(避免全局注册)

首先确保你的i18n配置不要把__函数注册到全局,而是让它挂载到每个请求对象上:

const i18n = require('i18n');
const express = require('express');
const app = express();

i18n.configure({
  locales: ['en', 'fr', 'es'],
  defaultLocale: 'en',
  directory: `${__dirname}/locales`, // 你的翻译文件目录
  objectNotation: true, // 支持嵌套翻译键
  // 不要设置 register: global!这会导致全局__函数,引发并发问题
});

// 让i18n挂载到每个req/res对象上
app.use(i18n.init);

步骤2:配置Nodemailer+Handlebars模板引擎

假设你用nodemailer和nodemailer-express-handlebars来处理邮件模板,这里不需要注册全局翻译助手,只需要配置模板路径即可:

const nodemailer = require('nodemailer');
const hbs = require('nodemailer-express-handlebars');

// 初始化邮件 transporter(根据你的邮箱服务商配置)
const transporter = nodemailer.createTransport({
  service: 'Gmail',
  auth: {
    user: 'your-email@example.com',
    pass: 'your-app-password'
  }
});

// 配置Handlebars模板引擎
transporter.use('compile', hbs({
  viewEngine: {
    extname: '.hbs',
    layoutsDir: `${__dirname}/views/email/layouts`,
    defaultLayout: 'main', // 可选的邮件布局模板
    partialsDir: `${__dirname}/views/email/partials` // 可选的模板片段
  },
  viewPath: `${__dirname}/views/email`,
  extName: '.hbs'
}));

步骤3:在请求处理中传递请求级翻译函数

在处理发送邮件的路由里,直接把当前请求的req.__函数传入邮件模板的上下文:

app.post('/send-welcome-email', (req, res) => {
  // req.__已经绑定了当前请求的locale(比如从请求头、URL参数或Cookie获取的)
  const mailOptions = {
    from: 'your-email@example.com',
    to: req.body.recipientEmail,
    // 先翻译邮件主题(直接用req.__)
    subject: req.__('email.welcome.subject'),
    template: 'welcome', // 对应views/email/welcome.hbs模板
    context: {
      username: req.body.username,
      // 把请求级的翻译函数传给模板上下文
      __: req.__
    }
  };

  transporter.sendMail(mailOptions, (err, info) => {
    if (err) {
      return res.status(500).json({ error: err.message });
    }
    res.json({ message: 'Email sent successfully', info: info.response });
  });
});

步骤4:在Handlebars模板中使用翻译函数

现在你可以在邮件模板里直接调用{{__}},它会使用当前请求的locale进行翻译:

<!-- views/email/welcome.hbs -->
<h1>{{__ 'email.welcome.heading' name=username}}</h1>
<p>{{__ 'email.welcome.body'}}</p>
<p>{{__ 'email.welcome.signoff'}}</p>

处理非请求上下文场景(如定时任务)

如果需要在没有请求的场景下发邮件(比如定时任务),可以手动指定locale,并创建一个绑定该locale的翻译函数:

const sendScheduledReminder = (recipientEmail, userLocale = 'en') => {
  // 临时保存当前全局locale,避免污染其他任务
  const originalLocale = i18n.getLocale();
  // 设置目标locale
  i18n.setLocale(userLocale);

  const mailOptions = {
    from: 'your-email@example.com',
    to: recipientEmail,
    subject: i18n.__('email.reminder.subject'),
    template: 'reminder',
    context: {
      __: (key, options) => i18n.__({ phrase: key, locale: userLocale }, options.hash)
    }
  };

  transporter.sendMail(mailOptions, (err) => {
    // 恢复全局locale
    i18n.setLocale(originalLocale);
    if (err) console.error('Failed to send scheduled email:', err);
  });
};

为什么这个方案有效?

  • 每个邮件渲染的__函数都是独立绑定到当前请求(或手动指定)的locale,完全隔离了并发请求的状态
  • 不需要全局修改Handlebars的助手,避免了全局状态带来的所有并发问题
  • 完全兼容Express的i18n中间件工作流,不需要额外的hack

内容的提问来源于stack exchange,提问作者Joris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:23:46