ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

React Email 国际化(i18n)完全指南:next-intl、react-intl 与 react-i18next 多语言邮件实战

React Email 国际化(i18n)完全指南:next-intl、react-intl 与 react-i18next 多语言邮件实战 React Email 国际化i18n完全指南next-intl、react-intl 与 react-i18next 多语言邮件实战【免费下载链接】react-email Build and send emails using React项目地址: https://gitcode.com/GitHub_Trending/re/react-email本指南基于 React Email 官方 i18n 文档skills/react-email/references/I18N.md编写系统讲解如何在 React Email 中为邮件模板引入多语言支持。React Email 官方支持 next-intl、react-intlFormatJS与 react-i18next 三种主流 i18n 库本文逐一给出安装、配置、模板改造与发送调用的完整实操方案并覆盖消息文件组织、RTL 语言、日期货币格式化、主题行翻译等最佳实践。读完本文你将能把自己的英文邮件模板平滑改造成支持任意数量语言环境的可发送多语言邮件。为什么邮件需要国际化邮件是与用户沟通最直接、最私密的方式用错语言会显著影响体验。React Email 将邮件编写为 React 组件因此可以复用成熟的 i18n 生态把硬编码文案抽离到按语言划分的消息文件运行时根据用户的语言环境locale动态渲染对应文案。官方文档明确表示React Email 对 next-intl、react-i18next、react-intl 三种库提供官方支持officially supports你可以按项目技术栈自由选择库定位适用场景next-intlNext.js 生态的一体化方案API 简洁基于 Next.js 的应用希望与 App Router 深度集成react-intlFormatJS强大的 ICU 消息格式化能力需要复数、日期、数字、货币等复杂格式化react-i18nexti18next 生态灵活可控非 Next.js 应用或需要更多底层控制三者的核心思路一致先创建每种语言的消息文件再在邮件组件中通过翻译函数读取文案。区别在于翻译函数的获取方式与消息文件组织形态。通用前提pixelBasedPreset 与 Tailwind 样式本文所有示例模板都使用了Tailwind组件并传入config{{ presets: [pixelBasedPreset] }}。pixelBasedPreset是react-email内置导出的 Tailwind 预设packages/react-email/src/components/tailwind/tailwind.tsx 中定义它把 Tailwind 默认的 rem 单位字号与间距重新映射为像素值——例如text-base对应16px行高24px、p-4对应16px内边距。由于大部分邮件客户端不支持rem单位使用该预设可保证样式在各客户端下渲染一致。实际项目中若使用自定义 Tailwind 配置只需将预设替换为你的配置即可i18n 改造方式完全不变。方案一next-intlNext.js 应用首选next-intl 是面向 Next.js 的国际化库API 直观适合绝大多数 Next.js 应用。1. 安装npm install next-intl2. 创建消息文件每种语言一个 JSON 文件以邮件模板名作为顶层命名空间// messages/en.json { welcome-email: { subject: Welcome to Acme, greeting: Hi, body: Thanks for signing up! Were excited to have you on board., cta: Get Started, footer: If you have questions, reply to this email. } }// messages/es.json { welcome-email: { subject: Bienvenido a Acme, greeting: Hola, body: ¡Gracias por registrarte! Estamos emocionados de tenerte en la plataforma., cta: Comenzar, footer: Si tienes preguntas, responde a este correo electrónico. } }// messages/fr.json { welcome-email: { subject: Bienvenue chez Acme, greeting: Bonjour, body: Merci de vous être inscrit ! Nous sommes ravis de vous accueillir., cta: Commencer, footer: Si vous avez des questions, répondez à cet e-mail. } }3. 改造邮件模板通过createTranslator在服务端创建翻译器namespace指定消息文件的顶层命名空间import { createTranslator } from next-intl; import { Html, Head, Preview, Body, Container, Heading, Text, Button, Hr, Tailwind, pixelBasedPreset } from react-email; interface WelcomeEmailProps { name: string; verificationUrl: string; locale: string; } export default async function WelcomeEmail({ name, verificationUrl, locale }: WelcomeEmailProps) { const t createTranslator({ messages: await import(../messages/${locale}.json), namespace: welcome-email, locale }); return ( Html lang{locale} Tailwind config{{ presets: [pixelBasedPreset] }} Head / Body classNamebg-gray-100 font-sans Preview{t(subject)}/Preview Container classNamemx-auto py-10 px-5 max-w-xl Heading classNametext-2xl font-bold text-gray-800 {t(subject)} /Heading Text classNametext-base leading-7 text-gray-800 my-4 {t(greeting)} {name}, /Text Text classNametext-base leading-7 text-gray-800 my-4 {t(body)} /Text Button href{verificationUrl} classNamebg-blue-600 text-white px-5 py-3 rounded block text-center no-underline box-border {t(cta)} /Button Hr classNameborder-solid border-gray-200 my-5 / Text classNametext-sm text-gray-500 {t(footer)} /Text /Container /Body /Tailwind /Html ); } // Preview props WelcomeEmail.PreviewProps { name: John, verificationUrl: https://example.com/verify, locale: en } as WelcomeEmailProps;要点说明组件必须声明为async因为createTranslator需要await import()动态加载消息文件Html lang{locale}将语言标记写入 HTML帮助邮件客户端正确识别内容语言使用createTranslator而非 Next.js App Router 中的useTranslationsHook 等 API是因为预览服务器preview server与渲染环境无法访问你在 Next.js 应用中定义的 i18n 上下文必须自建翻译器。4. 发送时传入 localeawait resend.emails.send({ from: Acme onboardingresend.dev, to: [userexample.com], subject: Welcome, react: WelcomeEmail nameJean verificationUrl... localefr / });locale作为 prop 传入后组件内部会加载messages/fr.json渲染法语内容。方案二react-intlFormatJS复杂格式化首选react-intl 基于 FormatJS其 ICU MessageFormat 语法擅长处理复数、日期、数字、货币等复杂格式化需求。1. 安装npm install react-intl2. 创建消息文件采用messages/locale/template.json的目录结构每个模板独立文件// messages/en/welcome-email.json { header: Welcome to Acme, greeting: Hi, body: Thanks for signing up!, cta: Get Started, itemCount: {count, plural, one {# item} other {# items}} }注意itemCount使用了 ICU 复数语法count为 1 时输出 1 item否则输出 n items。这正是 react-intl 相对其他方案的核心差异化能力。3. 在邮件中使用通过createIntl获取formatMessage以消息 id 引用文案import { createIntl } from react-intl; import { Html, Body, Container, Text, Button, Tailwind, pixelBasedPreset } from react-email; interface WelcomeEmailProps { name: string; locale: string; itemCount?: number; } export default async function WelcomeEmail({ name, locale, itemCount 1 }: WelcomeEmailProps) { const { formatMessage } createIntl({ locale, messages: await import(../messages/${locale}/welcome-email.json) }); return ( Html lang{locale} Tailwind config{{ presets: [pixelBasedPreset] }} Body classNamebg-gray-100 font-sans Container classNamemx-auto p-5 max-w-xl Text classNametext-base text-gray-800 {formatMessage({ id: greeting })} {name}, /Text Text classNametext-base text-gray-800 {formatMessage({ id: body })} /Text Text classNametext-base text-gray-800 {formatMessage({ id: itemCount }, { count: itemCount })} /Text Button hrefhttps://example.com classNamebg-blue-600 text-white px-5 py-3 rounded box-border {formatMessage({ id: cta })} /Button /Container /Body /Tailwind /Html ); }formatMessage({ id: itemCount }, { count: itemCount })将itemCount变量注入 ICU 消息formatMessage会自动按当前 locale 的复数规则选择正确形式。此外react-intl 还可在模板中直接使用FormattedNumber、FormattedDate、FormattedPlural等声明式组件适合在邮件正文中做更精细的格式化。方案三react-i18next非 Next.js 应用或需要更多控制react-i18next 是 i18next 的 React 绑定适合非 Next.js 应用或当你需要完全掌控翻译加载与缓存逻辑时。1. 安装npm install react-i18next i18next i18next-resources-to-backend三个包各司其职i18next是核心引擎react-i18next提供 React 集成i18next-resources-to-backend允许用动态import()按需加载语言资源。2. 配置 i18next 实例// i18n.js import i18next from i18next; import resourcesToBackend from i18next-resources-to-backend; import { initReactI18next } from react-i18next; i18next .use(initReactI18next) .use(resourcesToBackend((language, namespace) import(./messages/${language}/${namespace}.json) )) .init({ supportedLngs: [en, es, fr, de], fallbackLng: en, lng: undefined, preload: [en, es, fr, de] }); export { i18next };配置项说明supportedLngs声明支持的语言列表fallbackLng缺失翻译时的回退语言通常设为英文lng: undefined不锁定默认语言交由每次调用时指定preload在服务端Node 环境预先加载所有语言资源避免首次渲染时的异步竞态resourcesToBackend的回调(language, namespace) import(...)会在运行时按需加载messages/language/namespace.json。3. 创建服务端翻译辅助函数// get-t.js import { i18next } from ./i18n; export async function getT(namespace, locale) { if (locale i18next.resolvedLanguage ! locale) { await i18next.changeLanguage(locale); } if (namespace !i18next.hasLoadedNamespace(namespace)) { await i18next.loadNamespaces(namespace); } return { t: i18next.getFixedT( locale ?? i18next.resolvedLanguage, Array.isArray(namespace) ? namespace[0] : namespace ), i18n: i18next }; }getT的核心逻辑当目标 locale 与当前已解析语言不一致时先changeLanguage切换若命名空间尚未加载则loadNamespaces加载最终通过getFixedT(locale, namespace)返回绑定好语言与命名空间的翻译函数t。由于 i18next 实例是全局单例getT确保每次渲染都拿到正确的语言快照。4. 创建消息文件按messages/locale/template.json组织// messages/en/welcome-email.json { subject: Welcome to Acme, greeting: Hi, body: Thanks for signing up!, cta: Get Started }// messages/es/welcome-email.json { subject: Bienvenido a Acme, greeting: Hola, body: ¡Gracias por registrarte!, cta: Comenzar }5. 在邮件模板中使用import { getT } from ../get-t; import { Html, Body, Container, Heading, Text, Button, Tailwind, pixelBasedPreset } from react-email; interface WelcomeEmailProps { name: string; locale: string; } export default async function WelcomeEmail({ name, locale }: WelcomeEmailProps) { const { t } await getT(welcome-email, locale); return ( Html lang{locale} Tailwind config{{ presets: [pixelBasedPreset] }} Body classNamebg-gray-100 font-sans Container classNamemx-auto p-5 max-w-xl Heading classNametext-2xl font-bold text-gray-800 {t(subject)} /Heading Text classNametext-base text-gray-800 {t(greeting)} {name}, /Text Text classNametext-base text-gray-800 {t(body)} /Text Button hrefhttps://example.com classNamebg-blue-600 text-white px-5 py-3 rounded box-border {t(cta)} /Button /Container /Body /Tailwind /Html ); }消息文件组织方式按命名空间组织推荐两种常用形态形态 A每种语言一个聚合文件messages/ ├── en.json # All English translations │ ├── welcome-email │ ├── password-reset │ └── order-confirmation ├── es.json # All Spanish translations └── fr.json # All French translations适合 next-intl 风格namespace定位小项目或文案量少时维护成本最低。形态 B按模板拆分独立文件messages/ ├── en/ │ ├── welcome-email.json │ ├── password-reset.json │ └── order-confirmation.json ├── es/ │ ├── welcome-email.json │ ├── password-reset.json │ └── order-confirmation.json └── fr/ ├── welcome-email.json ├── password-reset.json └── order-confirmation.json适合 react-intl 与 react-i18next 风格每个邮件模板独立成文件便于团队分工与按需加载。仓库官方文档另有独立指南apps/docs/guides/internationalization/next-intl.mdx、apps/docs/guides/internationalization/react-i18next.mdx、apps/docs/guides/internationalization/react-intl.mdx。翻译键命名规范使用描述性、层级化的键名避免扁平、无结构的键{ welcome-email: { subject: Welcome!, preview: Get started with your account, header: { title: Welcome to Acme, subtitle: Were glad youre here }, body: { greeting: Hi, intro: Thanks for signing up!, next-steps: Heres how to get started: }, cta: { primary: Get Started, secondary: Learn More }, footer: { help: Need help? Reply to this email, unsubscribe: Unsubscribe from these emails } } }层级结构天然提供了语义分组与命名空间也便于在模板中通过t(body.greeting)精确取值。最佳实践1. 始终把 locale 设为必传 proplocale是邮件渲染的语言开关不应有默认值依赖interface EmailProps { locale: string; // other props... }2. 设置 HTML lang 属性React Email 的Html组件默认langen、dirltr见 packages/react-email/src/components/html/html.tsx显式传入lang{locale}可覆盖默认值Html lang{locale}这样邮件客户端、屏幕阅读器与垃圾邮件过滤器都能正确识别内容语言。3. 支持 RTL 语言对阿拉伯语、希伯来语等从右向左书写的语言需要同时设置dirconst isRTL [ar, he, fa].includes(locale); Html lang{locale} dir{isRTL ? rtl : ltr}Html组件的dir属性同样透传到最终的html标签源码默认ltr。4. 提供回退翻译当某语言文件缺失时回退到默认语言防止渲染崩溃const t createTranslator({ messages: await import(../messages/${locale}.json).catch(() import(../messages/en.json) ), locale, namespace: welcome-email });5. 逐一测试所有语言环境利用PreviewProps逐个验证各语言渲染效果——这是 React Email 预览服务器提供的本地调试机制WelcomeEmail.PreviewProps { name: Test User, locale: en // Change to test different locales } as WelcomeEmailProps;将locale依次改为es、fr等在本地预览服务器中检查文案、换行、按钮宽度是否正常。6. 保持各语言文件键一致所有语言文件的翻译键必须完全一致否则会出现键名漂移导致的缺失翻译// ✅ Good // en.json: { cta: Get Started } // es.json: { cta: Comenzar } // ❌ Bad // en.json: { button: Get Started } // es.json: { cta: Comenzar }7. 处理缺失翻译在翻译器上注册onError回调提前感知缺失键// With next-intl const t createTranslator({ messages, locale, namespace: welcome-email, onError: (error) { console.warn(Translation missing:, error); } });8. 不要忘记翻译主题行邮件主题subject是收件人最先看到的内容同样要翻译。主题行的翻译发生在组件之外可以在发送前单独创建翻译器获取const t createTranslator({...}); await resend.emails.send({ from: Acme onboardingresend.dev, to: [user.email], subject: t(subject), // ✅ Translated subject react: WelcomeEmail {...props} / });Preview组件中的{t(subject)}负责邮件预览区文案而这里的subject是真正的邮件主题行两者都需要翻译。9. 保持格式一致性不同语言对日期、时间、数字、货币的书写习惯差异巨大日期格式MM/DD/YYYY 与 DD/MM/YYYY时间格式12 小时制与 24 小时制数字分隔符1,234.56 与 1.234,56货币符号及位置$100 与 100$务必使用IntlAPIIntl.NumberFormat、Intl.DateTimeFormat按 locale 自动格式化而不是在模板中硬编码。仓库示例邮件中已有此实践例如 apps/demo/emails/Community/notifications/yelp-recent-login.tsx 与 apps/demo/emails/Community/reset-password/twitch-reset-password.tsx 都使用Intl.DateTimeFormat生成本地化日期。完整示例多语言订单确认邮件将以上实践整合实现一封支持多语言、RTL、本地化货币与日期的订单确认邮件import { createTranslator } from next-intl; import { Html, Head, Preview, Body, Container, Section, Heading, Text, Button, Hr, Tailwind, pixelBasedPreset } from react-email; interface OrderConfirmationProps { orderNumber: string; total: number; currency: string; locale: string; orderDate: Date; } export default async function OrderConfirmation({ orderNumber, total, currency, locale, orderDate }: OrderConfirmationProps) { const t createTranslator({ messages: await import(../messages/${locale}.json), namespace: order-confirmation, locale }); const isRTL [ar, he].includes(locale); const currencyFormatter new Intl.NumberFormat(locale, { style: currency, currency }); const dateFormatter new Intl.DateTimeFormat(locale, { year: numeric, month: long, day: numeric }); return ( Html lang{locale} dir{isRTL ? rtl : ltr} Tailwind config{{ presets: [pixelBasedPreset] }} Head / Body classNamebg-gray-100 font-sans Preview{t(preview)}/Preview Container classNamemx-auto py-10 px-5 max-w-xl Heading classNametext-2xl font-bold text-gray-800 {t(title)} /Heading Text classNametext-base text-gray-800 my-2 {t(order-number)}: {orderNumber} /Text Text classNametext-base text-gray-800 my-2 {t(order-date)}: {dateFormatter.format(orderDate)} /Text Section classNamebg-white p-5 rounded my-4 Text classNametext-xl font-bold text-gray-800 {t(total)}: {currencyFormatter.format(total)} /Text /Section Button href{https://example.com/orders/${orderNumber}} classNamebg-blue-600 text-white px-5 py-3 rounded block text-center no-underline my-5 box-border {t(view-order)} /Button Hr classNameborder-solid border-gray-200 my-5 / Text classNametext-sm text-gray-500 {t(footer)} /Text /Container /Body /Tailwind /Html ); }对应的消息文件以英文和西班牙语为例// messages/en.json { order-confirmation: { preview: Your order has been confirmed, title: Order Confirmed, order-number: Order number, order-date: Order date, total: Total, view-order: View Order, footer: Thank you for your purchase! } }// messages/es.json { order-confirmation: { preview: Tu pedido ha sido confirmado, title: Pedido Confirmado, order-number: Número de pedido, order-date: Fecha del pedido, total: Total, view-order: Ver Pedido, footer: ¡Gracias por tu compra! } }示例中的关键设计Intl.NumberFormat(locale, { style: currency, currency })按 locale 输出正确货币格式如英语的$1,234.56与西班牙语的1.234,56 US$Intl.DateTimeFormat(locale, {...})按 locale 输出本地化日期isRTL数组可扩展其他从右向左书写的语言如波斯语fa阿拉伯语、希伯来语环境下dirrtl会让整个布局镜像翻转配合 Tailwind 的盒模型样式仍能保持排版正确。三套方案速查与选型建议维度next-intlreact-intlreact-i18next安装命令npm install next-intlnpm install react-intlnpm install react-i18next i18next i18next-resources-to-backend核心 APIcreateTranslatorcreateIntlformatMessagegetTt基于 i18next 实例消息组织每语言一个聚合文件 namespace每语言每模板独立文件每语言每模板独立文件 命名空间复杂格式化借助IntlAPI原生 ICU 语法复数/日期/数字/货币借助IntlAPI最适场景Next.js 应用需要强格式化能力非 Next.js 或需底层控制选型建议Next.js 项目直接选用 next-intl邮件中含大量复数、货币、日期等复杂文案时优先 react-intlNode 或其他框架下希望深度掌控翻译加载流程时选 react-i18next。三种方案均要求邮件组件为async组件、locale为必传 prop并配合Html lang{locale}标记内容语言。若需深入学习各方案在官方文档中的独立教程可分别查阅 next-intl 指南、react-i18next 指南 与 react-intl 指南。【免费下载链接】react-email Build and send emails using React项目地址: https://gitcode.com/GitHub_Trending/re/react-email创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表