Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Email support #27

Merged
merged 5 commits into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ yarn-error.log*
# turbo
.turbo

dist
dist

.react-email
Binary file modified bun.lockb
Binary file not shown.
Binary file added examples/email/emails/static/vercel-arrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/email/emails/static/vercel-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/email/emails/static/vercel-team.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/email/emails/static/vercel-user.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
148 changes: 148 additions & 0 deletions examples/email/emails/vercel-invite-user.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { setupI18n } from "@languine/react-email";
import {
Body,
Button,
Column,
Container,
Head,
Heading,
Hr,
Html,
Img,
Link,
Preview,
Row,
Section,
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";

interface VercelInviteUserEmailProps {
locale: string;
username?: string;
userImage?: string;
invitedByUsername?: string;
invitedByEmail?: string;
teamName?: string;
teamImage?: string;
inviteLink?: string;
inviteFromIp?: string;
inviteFromLocation?: string;
}

const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "";

export const VercelInviteUserEmail = ({
locale = "es",
username = "alanturing",
userImage = `${baseUrl}/static/vercel-user.png`,
invitedByUsername = "Alan",
invitedByEmail = "[email protected]",
teamName = "Enigma",
teamImage = `${baseUrl}/static/vercel-team.png`,
inviteLink = "https://vercel.com/teams/invite/foo",
inviteFromIp = "204.13.186.218",
inviteFromLocation = "São Paulo, Brazil",
}: VercelInviteUserEmailProps) => {
const i18n = setupI18n(locale);

return (
<Html>
<Head />
<Preview>{i18n.t("previewText", { invitedByUsername })}</Preview>
<Tailwind>
<Body className="bg-white my-auto mx-auto font-sans px-2">
<Container className="border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] max-w-[465px]">
<Section className="mt-[32px]">
<Img
src={`${baseUrl}/static/vercel-logo.png`}
width="40"
height="37"
alt={i18n.t("logoAlt")}
className="my-0 mx-auto"
/>
</Section>
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
{i18n.t("joinTeamHeading", {
teamName: teamName,
company: "Vercel",
})}
</Heading>
<Text className="text-black text-[14px] leading-[24px]">
{i18n.t("greeting", { username })}
</Text>
<Text className="text-black text-[14px] leading-[24px]">
{i18n.t("invitationText", {
invitedByUsername: <strong>{invitedByUsername}</strong>,
email: (
<Link href={`mailto:${invitedByEmail}`}>
{invitedByEmail}
</Link>
),
teamName: <strong>{teamName}</strong>,
company: "Vercel",
})}
</Text>
<Section>
<Row>
<Column align="right">
<Img
className="rounded-full"
src={userImage}
width="64"
height="64"
/>
</Column>
<Column align="center">
<Img
src={`${baseUrl}/static/vercel-arrow.png`}
width="12"
height="9"
alt={i18n.t("invitedToAlt")}
/>
</Column>
<Column align="left">
<Img
className="rounded-full"
src={teamImage}
width="64"
height="64"
/>
</Column>
</Row>
</Section>
<Section className="text-center mt-[32px] mb-[32px]">
<Button
className="bg-[#000000] rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3"
href={inviteLink}
>
{i18n.t("joinTeamButton")}
</Button>
</Section>
<Text className="text-black text-[14px] leading-[24px]">
{i18n.t("copyUrlText")}{" "}
<Link href={inviteLink} className="text-blue-600 no-underline">
{inviteLink}
</Link>
</Text>
<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
<Text className="text-[#666666] text-[12px] leading-[24px]">
{i18n.t("footerText", {
username: <span className="text-black">{username}</span>,
ip: <span className="text-black">{inviteFromIp}</span>,
location: (
<span className="text-black">{inviteFromLocation}</span>
),
})}
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
};

export default VercelInviteUserEmail;
18 changes: 18 additions & 0 deletions examples/email/languine.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig } from "languine";

export default defineConfig({
version: "7.0.0",
locale: {
source: "en",
targets: ["es", "sv", "pt"],
},
files: {
json: {
include: ["locales/[locale].json"],
},
},
llm: {
provider: "openai",
model: "gpt-4-turbo",
},
});
15 changes: 15 additions & 0 deletions examples/email/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"previewText": "Join %{invitedByUsername} on %{company}",
"company": "%{company}",
"logoAlt": "Vercel Logo",
"joinTeamHeading": "Join %{teamName} on %{company}",
"greeting": "Hi %{username},",
"invitationText":
"%{invitedByUsername} (%{email}) has invited you to join the %{teamName} team on %{company}.",
"invitedToAlt": "Invited to",
"joinTeamButton": "Join the team",
"copyUrlText": "Or copy and paste this URL into your browser:",
"footerText":
"This invitation was intended for %{username} (%{ip} from %{location}). If you were not expecting this invitation, you can ignore this email. If you are concerned about your account's safety, please reply to this email to get in touch with us."
}

12 changes: 12 additions & 0 deletions examples/email/locales/es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"previewText": "Únete a %{invitedByUsername} en %{company}",
"company": "%{company}",
"logoAlt": "Logo de Vercel",
"joinTeamHeading": "Únete al equipo %{teamName} en %{company}",
"greeting": "Hola %{username},",
"invitationText": "%{invitedByUsername} (%{email}) te ha invitado a unirte al equipo %{teamName} en %{company}.",
"invitedToAlt": "Invitado a",
"joinTeamButton": "Únete al equipo",
"copyUrlText": "O copia y pega esta URL en tu navegador:",
"footerText": "Esta invitación fue destinada para %{username} (%{ip} desde %{location}). Si no esperabas esta invitación, puedes ignorar este correo electrónico. Si te preocupa la seguridad de tu cuenta, por favor responde a este correo electrónico para ponerte en contacto con nosotros."
}
12 changes: 12 additions & 0 deletions examples/email/locales/pt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"previewText": "Junte-se a %{invitedByUsername} na %{company}",
"company": "%{company}",
"logoAlt": "Logotipo da Vercel",
"joinTeamHeading": "Junte-se ao time %{teamName} na %{company}",
"greeting": "Oi %{username},",
"invitationText": "%{invitedByUsername} (%{email}) convidou você para se juntar ao time %{teamName} na %{company}.",
"invitedToAlt": "Convidado para",
"joinTeamButton": "Entrar no time",
"copyUrlText": "Ou copie e cole este URL no seu navegador:",
"footerText": "Este convite foi destinado para %{username} (%{ip} de %{location}). Se você não estava esperando este convite, pode ignorar este e-mail. Se estiver preocupado com a segurança da sua conta, por favor responda a este e-mail para entrar em contato conosco."
}
12 changes: 12 additions & 0 deletions examples/email/locales/sv.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"previewText": "Gå med %{invitedByUsername} på %{company}",
"company": "%{company}",
"logoAlt": "Vercel-logotyp",
"joinTeamHeading": "Gå med i %{teamName} på %{company}",
"greeting": "Hej %{username},",
"invitationText": "%{invitedByUsername} (%{email}) har bjudit in dig att gå med i %{teamName}-teamet på %{company}.",
"invitedToAlt": "Inbjuden till",
"joinTeamButton": "Gå med i teamet",
"copyUrlText": "Eller kopiera och klistra in denna URL i din webbläsare:",
"footerText": "Denna inbjudan var avsedd för %{username} (%{ip} från %{location}). Om du inte förväntade dig denna inbjudan kan du ignorera detta e-postmeddelande. Om du är orolig för säkerheten på ditt konto, vänligen svara på detta e-postmeddelande för att komma i kontakt med oss."
}
20 changes: 20 additions & 0 deletions examples/email/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "react-email-starter",
"version": "0.1.6",
"private": true,
"scripts": {
"build": "email build",
"dev": "email dev",
"export": "email export"
},
"dependencies": {
"@react-email/components": "0.0.31",
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@types/react": "19.0.1",
"@types/react-dom": "19.0.1",
"react-email": "3.0.4"
}
}
27 changes: 27 additions & 0 deletions examples/email/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# React Email Starter

A live preview right in your browser so you don't need to keep sending real emails during development.

## Getting Started

First, install the dependencies:

```sh
npm install
# or
yarn
```

Then, run the development server:

```sh
npm run dev
# or
yarn dev
```

Open [localhost:3000](http://localhost:3000) with your browser to see the result.

## License

MIT License
1 change: 0 additions & 1 deletion examples/next-international/languine.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ export default defineConfig({
llm: {
provider: "ollama",
model: "mistral:latest",
temperature: 0,
},
});
60 changes: 60 additions & 0 deletions packages/react-email/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<p align="center">
<img src="https://github.com/midday-ai/languine/blob/main/packages/react-email/image.png" />
</p>

<p align="center">
A lightweight i18n library for React email templates.
</p>

---

```bash
$ npm install @languine/react-email
```

## What is this?

This is a lightweight i18n library for React email templates. It is built on top of `i18n-js`.

Automatically included language files are in the `locales` folder.

## How to use

```tsx
import { setupI18n } from "@languine/react-email";

export function WelcomeEmail({ locale, name }) {
const i18n = setupI18n(locale);

return (
<Html>
<Head />
<Preview>{i18n.t("preview")}</Preview>
<Body>
<Text>{i18n.t("welcome", { name })}</Text>
</Body>
</Html>
);
}
```

### Rendering the email
```tsx
import { render } from '@react-email/render';
import { WelcomeEmail } from "./emails/welcome";

const html = await render(<WelcomeEmail locale="en" name="John" />, {
pretty: true,
});

console.log(html);
```


## Works together with Languine CLI

Automatically add and translate your email templates with [Languine CLI](https://languine.ai).

```bash
$ npx languine@latest
```
Binary file added packages/react-email/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions packages/react-email/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@languine/react-email",
"version": "0.1.0",
"files": ["dist", "README.md"],
"main": "dist/index.mjs",
"types": "dist/index.d.ts",
"license": "MIT",
"scripts": {
"clean": "rm -rf .turbo node_modules",
"lint": "biome check .",
"format": "biome format --write .",
"typecheck": "tsc --noEmit",
"build": "tsup --clean src/index.tsx"
},
"devDependencies": {
"tsup": "^8.3.5",
"typescript": "^5.7.2"
},
"dependencies": {
"i18n-js": "^4.5.1",
"react-string-replace": "^1.1.1"
}
}
Loading
Loading