A live preview right in your browser so you don't need to keep sending real emails during development. Now with internationalization.
First, install the dependencies:
npm install
# or
yarn
Then, run the development server:
npm run dev
# or
yarn dev
Open localhost:3000 with your browser to see the result.
.
├── scripts
│ └── dev.mjs #<-- this script generates intl templates
├── src
│ ├── components #<-- reusable react components
│ ├── emails
│ │ ├── notion-magic-link #<-- template that supports intl
│ │ │ ├── email.tsx #<-- template react code
│ │ │ └── locales.ts #<-- template intl dictionaries
│ │ ...
│ │ └── vercel-invite-user.tsx #<-- template that doesn't supports intl
│ ├── root-locales.ts #<-- intl dictionaries global to all templates
│ ├── types.ts #<-- intl type safety enforcing helpers
│ └── utils
│ ├── create-locales.ts
│ ├── formatter.tsx #<-- bold formatter util
│ ├── get-default-prop.ts
│ └── get-locales.ts #<-- internationalization util
├── static #<-- static assets, like images
├── compiled #<-- output of dev script
│ ├── notion-magic-link #<-- result of template that supports intl
│ │ ├── notion-magic-link-en.tsx
│ │ ├── notion-magic-link-es.tsx
│ │ └── notion-magic-link-pt.tsx
│ ...
│ └── vercel-invite-user.tsx #<-- result of template didn't supported intl
...
You can take a look to the notion-magic-link
email template i left as example. But it works as follows:
-
Email templates go in
emails
folder. Those that support intl must be named asemail.tsx
and contained in a folder with the name of the template. -
To make a email template support intl, you must use
getLocales(lang, dictionaries)
util, example:import { TLanguage } from "@/types"; import { getLocales } from "@/utils/get-locales"; export const Email = ({ lang }: { lang: TLanguage }) => { const { // global key PREVIEW, // email keys LOGIN_HEADING, LOGIN_CODE_TEXT, // ... } = getLocales(lang, dictionaries); //...
Where:
lang
is the language prop of a templatedictionaries
are the imported key-values of each language, taken fromlocales.ts
, more on that below.
-
Language dictionaries used by a specific email template must be placed in its folder, inside the
locales.ts
.const locales = { es: { PREVIEW: "Haz click aquí para iniciar sesión con este enlace mágico", LOGIN_HEADING: "Iniciar sesión", }, en: { PREVIEW: "Click here to login with this magic link", LOGIN_HEADING: "Login", }, //... other languages, set at utils/create-locales and types.TLanguage }; export default locales;
You can create Components intl support. You just have to merge their interfaces with the global dictionary types to achieve type safety. If so, their intl keys are merged by getLocales
util.
Say that you need a footer for most of your templates. The suggested implementation goes as follows:
-
Create your component with the desired labels as dictionary keys:
export interface ISocialFooterProps { labels: { FOOTER_PRIVACY: string; FOOTER_CONTACT: string; FOOTER_DOWNLOAD_LABEL: string; }; } export const SocialFooter = ({ labels: { FOOTER_PRIVACY, FOOTER_CONTACT, FOOTER_DOWNLOAD_LABEL }, }: ISocialFooterProps) => { //... rest of the component definition };
-
Append new labels to your internationalized dictionaries. You can do this in various ways, but for simplicity's sake, here we will just add them to
root-locales.ts
global dictionary:const rootLocales = { es: { //... other key-value pairs FOOTER_PRIVACY: "Nuestros terminos", FOOTER_CONTACT: "Contactanos", FOOTER_DOWNLOAD_LABEL: "Descarga nuestra app en:", }, en: { //... other key-value pairs FOOTER_PRIVACY: "Privacy policy", FOOTER_CONTACT: "Contact us", FOOTER_DOWNLOAD_LABEL: "Download our app on:", }, }; export default rootLocales;
-
Add those types to the global dict types to achieve type safety:
export type TExtendedIntlDictionary<T> = TBaseIntlDictionary<T> & ISocialFooterProps["labels"]; // <-- appended types // ... & other component intl keys as types
-
Then, in your template, use your newly created component and its internationalized content labels as desired:
export const Email = ({ lang }: { lang: TLanguage }) => { const { // global translations PREVIEW, // email translations LOGIN_HEADING, LOGIN_CODE_TEXT, // component specific translations ...footerLabels } = getLocales(lang, dictionaries); //... return ( //... email render <SocialFooter labels={footerLabels} /> )
MIT License