mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 09:12:02 +10:00
feat: i18n for emails (#1442)
## Description Support setting a document language that will control the language used for sending emails to recipients. Additional work has been done to convert all emails to using our i18n implementation so we can later add controls for sending other kinds of emails in a users target language. ## Related Issue N/A ## Changes Made - Added `<Trans>` and `msg` macros to emails - Introduced a new `renderEmailWithI18N` utility in the lib package - Updated all emails to use the `<Tailwind>` component at the top level due to rendering constraints - Updated the `i18n.server.tsx` file to not use a top level await ## Testing Performed - Configured document language and verified emails were sent in the expected language - Created a document from a template and verified that the templates language was transferred to the document
This commit is contained in:
@ -1,3 +1,6 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { Button, Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -10,17 +13,21 @@ export const TemplateConfirmationEmail = ({
|
||||
confirmationLink,
|
||||
assetBaseUrl,
|
||||
}: TemplateConfirmationEmailProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
|
||||
|
||||
<Section className="flex-row items-center justify-center">
|
||||
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||
Welcome to Documenso!
|
||||
<Trans>Welcome to Documenso!</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
Before you get started, please confirm your email address by clicking the button below:
|
||||
<Trans>
|
||||
Before you get started, please confirm your email address by clicking the button below:
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -28,11 +35,13 @@ export const TemplateConfirmationEmail = ({
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={confirmationLink}
|
||||
>
|
||||
Confirm email
|
||||
<Trans>Confirm email</Trans>
|
||||
</Button>
|
||||
<Text className="mt-8 text-center text-sm italic text-slate-400">
|
||||
You can also copy and paste this link into your browser: {confirmationLink} (link
|
||||
expires in 1 hour)
|
||||
<Trans>
|
||||
You can also copy and paste this link into your browser: {confirmationLink} (link
|
||||
expires in 1 hour)
|
||||
</Trans>
|
||||
</Text>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -19,16 +21,18 @@ export const TemplateDocumentCancel = ({
|
||||
|
||||
<Section>
|
||||
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||
{inviterName} has cancelled the document
|
||||
<br />"{documentName}"
|
||||
<Trans>
|
||||
{inviterName} has cancelled the document
|
||||
<br />"{documentName}"
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
All signatures have been voided.
|
||||
<Trans>All signatures have been voided.</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
You don't need to sign it anymore.
|
||||
<Trans>You don't need to sign it anymore.</Trans>
|
||||
</Text>
|
||||
</Section>
|
||||
</>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Button, Column, Img, Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -30,17 +32,17 @@ export const TemplateDocumentCompleted = ({
|
||||
src={getAssetUrl('/static/completed.png')}
|
||||
className="-mt-0.5 mr-2 inline h-7 w-7 align-middle"
|
||||
/>
|
||||
Completed
|
||||
<Trans>Completed</Trans>
|
||||
</Text>
|
||||
</Column>
|
||||
</Section>
|
||||
|
||||
<Text className="text-primary mb-0 text-center text-lg font-semibold">
|
||||
{customBody ?? `“${documentName}” was signed by all signers`}
|
||||
<Trans>{customBody ?? `“${documentName}” was signed by all signers`}</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
Continue by downloading the document.
|
||||
<Trans>Continue by downloading the document.</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -59,7 +61,7 @@ export const TemplateDocumentCompleted = ({
|
||||
src={getAssetUrl('/static/download.png')}
|
||||
className="mb-0.5 mr-2 inline h-5 w-5 align-middle"
|
||||
/>
|
||||
Download
|
||||
<Trans>Download</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { RECIPIENT_ROLES_DESCRIPTION_ENG } from '@documenso/lib/constants/recipient-roles';
|
||||
import type { RecipientRole } from '@documenso/prisma/client';
|
||||
|
||||
@ -26,6 +29,8 @@ export const TemplateDocumentInvite = ({
|
||||
isTeamInvite,
|
||||
teamName,
|
||||
}: TemplateDocumentInviteProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { actionVerb, progressiveVerb } = RECIPIENT_ROLES_DESCRIPTION_ENG[role];
|
||||
|
||||
return (
|
||||
@ -35,28 +40,30 @@ export const TemplateDocumentInvite = ({
|
||||
<Section>
|
||||
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||
{selfSigner ? (
|
||||
<>
|
||||
{`Please ${actionVerb.toLowerCase()} your document`}
|
||||
<Trans>
|
||||
{`Please ${_(actionVerb).toLowerCase()} your document`}
|
||||
<br />
|
||||
{`"${documentName}"`}
|
||||
</>
|
||||
</Trans>
|
||||
) : isTeamInvite ? (
|
||||
<>
|
||||
{`${inviterName} on behalf of ${teamName} has invited you to ${actionVerb.toLowerCase()}`}
|
||||
<Trans>
|
||||
{`${inviterName} on behalf of ${teamName} has invited you to ${_(
|
||||
actionVerb,
|
||||
).toLowerCase()}`}
|
||||
<br />
|
||||
{`"${documentName}"`}
|
||||
</>
|
||||
</Trans>
|
||||
) : (
|
||||
<>
|
||||
{`${inviterName} has invited you to ${actionVerb.toLowerCase()}`}
|
||||
<Trans>
|
||||
{`${inviterName} has invited you to ${_(actionVerb).toLowerCase()}`}
|
||||
<br />
|
||||
{`"${documentName}"`}
|
||||
</>
|
||||
</Trans>
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
Continue by {progressiveVerb.toLowerCase()} the document.
|
||||
<Trans>Continue by {_(progressiveVerb).toLowerCase()} the document.</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -64,7 +71,7 @@ export const TemplateDocumentInvite = ({
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={signDocumentLink}
|
||||
>
|
||||
{actionVerb} Document
|
||||
<Trans>{_(actionVerb)} Document</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Column, Img, Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -26,19 +28,21 @@ export const TemplateDocumentPending = ({
|
||||
src={getAssetUrl('/static/clock.png')}
|
||||
className="-mt-0.5 mr-2 inline h-7 w-7 align-middle"
|
||||
/>
|
||||
Waiting for others
|
||||
<Trans>Waiting for others</Trans>
|
||||
</Text>
|
||||
</Column>
|
||||
</Section>
|
||||
|
||||
<Text className="text-primary mb-0 text-center text-lg font-semibold">
|
||||
“{documentName}” has been signed
|
||||
<Trans>“{documentName}” has been signed</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mb-6 mt-1 max-w-[80%] text-center text-base text-slate-400">
|
||||
We're still waiting for other signers to sign this document.
|
||||
<br />
|
||||
We'll notify you as soon as it's ready.
|
||||
<Trans>
|
||||
We're still waiting for other signers to sign this document.
|
||||
<br />
|
||||
We'll notify you as soon as it's ready.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Section>
|
||||
</>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { env } from 'next-runtime-env';
|
||||
|
||||
import { Button, Column, Img, Link, Section, Text } from '../components';
|
||||
@ -32,25 +33,27 @@ export const TemplateDocumentSelfSigned = ({
|
||||
src={getAssetUrl('/static/completed.png')}
|
||||
className="-mt-0.5 mr-2 inline h-7 w-7 align-middle"
|
||||
/>
|
||||
Completed
|
||||
<Trans>Completed</Trans>
|
||||
</Text>
|
||||
</Column>
|
||||
</Section>
|
||||
|
||||
<Text className="text-primary mb-0 mt-6 text-center text-lg font-semibold">
|
||||
You have signed “{documentName}”
|
||||
<Trans>You have signed “{documentName}”</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mb-6 mt-1 max-w-[80%] text-center text-base text-slate-400">
|
||||
Create a{' '}
|
||||
<Link
|
||||
href={signUpUrl}
|
||||
target="_blank"
|
||||
className="text-documenso-700 hover:text-documenso-600 whitespace-nowrap"
|
||||
>
|
||||
free account
|
||||
</Link>{' '}
|
||||
to access your signed documents at any time.
|
||||
<Trans>
|
||||
Create a{' '}
|
||||
<Link
|
||||
href={signUpUrl}
|
||||
target="_blank"
|
||||
className="text-documenso-700 hover:text-documenso-600 whitespace-nowrap"
|
||||
>
|
||||
free account
|
||||
</Link>{' '}
|
||||
to access your signed documents at any time.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -62,7 +65,7 @@ export const TemplateDocumentSelfSigned = ({
|
||||
src={getAssetUrl('/static/user-plus.png')}
|
||||
className="mb-0.5 mr-2 inline h-5 w-5 align-middle"
|
||||
/>
|
||||
Create account
|
||||
<Trans>Create account</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -73,7 +76,7 @@ export const TemplateDocumentSelfSigned = ({
|
||||
src={getAssetUrl('/static/review.png')}
|
||||
className="mb-0.5 mr-2 inline h-5 w-5 align-middle"
|
||||
/>
|
||||
View plans
|
||||
<Trans>View plans</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -18,20 +20,22 @@ export const TemplateDocumentDelete = ({
|
||||
|
||||
<Section>
|
||||
<Text className="text-primary mb-0 mt-6 text-left text-lg font-semibold">
|
||||
Your document has been deleted by an admin!
|
||||
<Trans>Your document has been deleted by an admin!</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mb-6 mt-1 text-left text-base text-slate-400">
|
||||
"{documentName}" has been deleted by an admin.
|
||||
<Trans>"{documentName}" has been deleted by an admin.</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mb-6 mt-1 text-left text-base text-slate-400">
|
||||
This document can not be recovered, if you would like to dispute the reason for future
|
||||
documents please contact support.
|
||||
<Trans>
|
||||
This document can not be recovered, if you would like to dispute the reason for future
|
||||
documents please contact support.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mt-1 text-left text-base text-slate-400">
|
||||
The reason provided for deletion is the following:
|
||||
<Trans>The reason provided for deletion is the following:</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="mx-auto mb-6 mt-1 text-left text-base italic text-slate-400">
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Link, Section, Text } from '../components';
|
||||
|
||||
export type TemplateFooterProps = {
|
||||
@ -9,10 +11,12 @@ export const TemplateFooter = ({ isDocument = true }: TemplateFooterProps) => {
|
||||
<Section>
|
||||
{isDocument && (
|
||||
<Text className="my-4 text-base text-slate-400">
|
||||
This document was sent using{' '}
|
||||
<Link className="text-[#7AC455]" href="https://documen.so/mail-footer">
|
||||
Documenso.
|
||||
</Link>
|
||||
<Trans>
|
||||
This document was sent using{' '}
|
||||
<Link className="text-[#7AC455]" href="https://documen.so/mail-footer">
|
||||
Documenso.
|
||||
</Link>
|
||||
</Trans>
|
||||
</Text>
|
||||
)}
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Button, Section, Text } from '../components';
|
||||
import { TemplateDocumentImage } from './template-document-image';
|
||||
|
||||
@ -16,11 +18,11 @@ export const TemplateForgotPassword = ({
|
||||
|
||||
<Section className="flex-row items-center justify-center">
|
||||
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||
Forgot your password?
|
||||
<Trans>Forgot your password?</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
That's okay, it happens! Click the button below to reset your password.
|
||||
<Trans>That's okay, it happens! Click the button below to reset your password.</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -28,7 +30,7 @@ export const TemplateForgotPassword = ({
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={resetPasswordLink}
|
||||
>
|
||||
Reset Password
|
||||
<Trans>Reset Password</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { env } from 'next-runtime-env';
|
||||
|
||||
import { Button, Section, Text } from '../components';
|
||||
@ -18,11 +19,11 @@ export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordPro
|
||||
|
||||
<Section className="flex-row items-center justify-center">
|
||||
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||
Password updated!
|
||||
<Trans>Password updated!</Trans>
|
||||
</Text>
|
||||
|
||||
<Text className="my-1 text-center text-base text-slate-400">
|
||||
Your password has been updated.
|
||||
<Trans>Your password has been updated.</Trans>
|
||||
</Text>
|
||||
|
||||
<Section className="mb-6 mt-8 text-center">
|
||||
@ -30,7 +31,7 @@ export const TemplateResetPassword = ({ assetBaseUrl }: TemplateResetPasswordPro
|
||||
className="bg-documenso-500 inline-flex items-center justify-center rounded-lg px-6 py-3 text-center text-sm font-medium text-black no-underline"
|
||||
href={`${NEXT_PUBLIC_WEBAPP_URL ?? 'http://localhost:3000'}/signin`}
|
||||
>
|
||||
Sign In
|
||||
<Trans>Sign In</Trans>
|
||||
</Button>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
Reference in New Issue
Block a user