mirror of
https://github.com/documenso/documenso.git
synced 2025-11-17 10:11:35 +10:00
Merge branch 'main' into staging
This commit is contained in:
@ -7,58 +7,94 @@ import { setupI18n } from '@lingui/core';
|
||||
import { setI18n } from '@lingui/react/server';
|
||||
|
||||
import { IS_APP_WEB } from '../../constants/app';
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '../../constants/i18n';
|
||||
import {
|
||||
APP_I18N_OPTIONS,
|
||||
SUPPORTED_LANGUAGE_CODES,
|
||||
isValidLanguageCode,
|
||||
} from '../../constants/i18n';
|
||||
import { extractLocaleData } from '../../utils/i18n';
|
||||
import { remember } from '../../utils/remember';
|
||||
|
||||
type SupportedLanguages = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||
|
||||
async function loadCatalog(lang: SupportedLanguages): Promise<{
|
||||
export async function loadCatalog(lang: SupportedLanguages): Promise<{
|
||||
[k: string]: Messages;
|
||||
}> {
|
||||
const extension = process.env.NODE_ENV === 'development' ? 'po' : 'js';
|
||||
const context = IS_APP_WEB ? 'web' : 'marketing';
|
||||
|
||||
const { messages } = await import(`../../translations/${lang}/${context}.${extension}`);
|
||||
let { messages } = await import(`../../translations/${lang}/${context}.${extension}`);
|
||||
|
||||
if (extension === 'po') {
|
||||
const { messages: commonMessages } = await import(
|
||||
`../../translations/${lang}/common.${extension}`
|
||||
);
|
||||
|
||||
messages = { ...messages, ...commonMessages };
|
||||
}
|
||||
|
||||
return {
|
||||
[lang]: messages,
|
||||
};
|
||||
}
|
||||
|
||||
const catalogs = await Promise.all(SUPPORTED_LANGUAGE_CODES.map(loadCatalog));
|
||||
const catalogs = Promise.all(SUPPORTED_LANGUAGE_CODES.map(loadCatalog));
|
||||
|
||||
// transform array of catalogs into a single object
|
||||
export const allMessages = catalogs.reduce((acc, oneCatalog) => {
|
||||
return { ...acc, ...oneCatalog };
|
||||
}, {});
|
||||
const allMessages = async () => {
|
||||
return await catalogs.then((catalogs) =>
|
||||
catalogs.reduce((acc, oneCatalog) => {
|
||||
return {
|
||||
...acc,
|
||||
...oneCatalog,
|
||||
};
|
||||
}, {}),
|
||||
);
|
||||
};
|
||||
|
||||
type AllI18nInstances = { [K in SupportedLanguages]: I18n };
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
export const allI18nInstances = SUPPORTED_LANGUAGE_CODES.reduce((acc, lang) => {
|
||||
const messages = allMessages[lang] ?? {};
|
||||
export const allI18nInstances = remember('i18n.allI18nInstances', async () => {
|
||||
const loadedMessages = await allMessages();
|
||||
|
||||
const i18n = setupI18n({
|
||||
locale: lang,
|
||||
messages: { [lang]: messages },
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return SUPPORTED_LANGUAGE_CODES.reduce((acc, lang) => {
|
||||
const messages = loadedMessages[lang] ?? {};
|
||||
|
||||
return { ...acc, [lang]: i18n };
|
||||
}, {}) as AllI18nInstances;
|
||||
const i18n = setupI18n({
|
||||
locale: lang,
|
||||
messages: { [lang]: messages },
|
||||
});
|
||||
|
||||
return { ...acc, [lang]: i18n };
|
||||
}, {}) as AllI18nInstances;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export const getI18nInstance = async (lang?: SupportedLanguages | (string & {})) => {
|
||||
const instances = await allI18nInstances;
|
||||
|
||||
if (!isValidLanguageCode(lang)) {
|
||||
return instances[APP_I18N_OPTIONS.sourceLang];
|
||||
}
|
||||
|
||||
return instances[lang] ?? instances[APP_I18N_OPTIONS.sourceLang];
|
||||
};
|
||||
|
||||
/**
|
||||
* This needs to be run in all layouts and page server components that require i18n.
|
||||
*
|
||||
* https://lingui.dev/tutorials/react-rsc#pages-layouts-and-lingui
|
||||
*/
|
||||
export const setupI18nSSR = () => {
|
||||
export const setupI18nSSR = async () => {
|
||||
const { lang, locales } = extractLocaleData({
|
||||
cookies: cookies(),
|
||||
headers: headers(),
|
||||
});
|
||||
|
||||
// Get and set a ready-made i18n instance for the given language.
|
||||
const i18n = allI18nInstances[lang];
|
||||
const i18n = await getI18nInstance(lang);
|
||||
|
||||
// Reactivate the i18n instance with the locale for date and number formatting.
|
||||
i18n.activate(lang, locales);
|
||||
|
||||
@ -47,3 +47,6 @@ export const SUPPORTED_LANGUAGES: Record<string, SupportedLanguage> = {
|
||||
short: 'es',
|
||||
},
|
||||
} satisfies Record<SupportedLanguageCodes, SupportedLanguage>;
|
||||
|
||||
export const isValidLanguageCode = (code: unknown): code is SupportedLanguageCodes =>
|
||||
SUPPORTED_LANGUAGE_CODES.includes(code as SupportedLanguageCodes);
|
||||
|
||||
@ -41,31 +41,35 @@ export const RECIPIENT_ROLES_DESCRIPTION_ENG = {
|
||||
actioned: `Approved`,
|
||||
progressiveVerb: `Approving`,
|
||||
roleName: `Approver`,
|
||||
roleNamePlural: msg`Approvers`,
|
||||
},
|
||||
[RecipientRole.CC]: {
|
||||
actionVerb: `CC`,
|
||||
actioned: `CC'd`,
|
||||
progressiveVerb: `CC`,
|
||||
roleName: `Cc`,
|
||||
roleNamePlural: msg`Ccers`,
|
||||
},
|
||||
[RecipientRole.SIGNER]: {
|
||||
actionVerb: `Sign`,
|
||||
actioned: `Signed`,
|
||||
progressiveVerb: `Signing`,
|
||||
roleName: `Signer`,
|
||||
roleNamePlural: msg`Signers`,
|
||||
},
|
||||
[RecipientRole.VIEWER]: {
|
||||
actionVerb: `View`,
|
||||
actioned: `Viewed`,
|
||||
progressiveVerb: `Viewing`,
|
||||
roleName: `Viewer`,
|
||||
roleNamePlural: msg`Viewers`,
|
||||
},
|
||||
} satisfies Record<keyof typeof RecipientRole, unknown>;
|
||||
|
||||
export const RECIPIENT_ROLE_TO_EMAIL_TYPE = {
|
||||
[RecipientRole.SIGNER]: 'SIGNING_REQUEST',
|
||||
[RecipientRole.VIEWER]: 'VIEW_REQUEST',
|
||||
[RecipientRole.APPROVER]: 'APPROVE_REQUEST',
|
||||
[RecipientRole.SIGNER]: `SIGNING_REQUEST`,
|
||||
[RecipientRole.VIEWER]: `VIEW_REQUEST`,
|
||||
[RecipientRole.APPROVER]: `APPROVE_REQUEST`,
|
||||
} as const;
|
||||
|
||||
export const RECIPIENT_ROLE_SIGNING_REASONS = {
|
||||
|
||||
@ -43,18 +43,10 @@ export class LocalJobProvider extends BaseJobProvider {
|
||||
}
|
||||
|
||||
public async triggerJob(options: SimpleTriggerJobOptions) {
|
||||
console.log({ jobDefinitions: this._jobDefinitions });
|
||||
|
||||
const eligibleJobs = Object.values(this._jobDefinitions).filter(
|
||||
(job) => job.trigger.name === options.name,
|
||||
);
|
||||
|
||||
console.log({ options });
|
||||
console.log(
|
||||
'Eligible jobs:',
|
||||
eligibleJobs.map((job) => job.name),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
eligibleJobs.map(async (job) => {
|
||||
// Ideally we will change this to a createMany with returning later once we upgrade Prisma
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import DocumentInviteEmailTemplate from '@documenso/email/templates/document-invite';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
@ -13,6 +13,7 @@ import {
|
||||
SendStatus,
|
||||
} from '@documenso/prisma/client';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../../constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
||||
import {
|
||||
@ -23,6 +24,7 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs';
|
||||
import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '../../../utils/document-audit-logs';
|
||||
import { renderCustomEmailTemplate } from '../../../utils/render-custom-email-template';
|
||||
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||
import { type JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_SIGNING_EMAIL_JOB_DEFINITION_ID = 'send.signing.requested.email';
|
||||
@ -90,22 +92,32 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
const recipientActionVerb =
|
||||
RECIPIENT_ROLES_DESCRIPTION_ENG[recipient.role].actionVerb.toLowerCase();
|
||||
|
||||
const i18n = await getI18nInstance(documentMeta?.language);
|
||||
|
||||
let emailMessage = customEmail?.message || '';
|
||||
let emailSubject = `Please ${recipientActionVerb} this document`;
|
||||
let emailSubject = i18n._(msg`Please ${recipientActionVerb} this document`);
|
||||
|
||||
if (selfSigner) {
|
||||
emailMessage = `You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`;
|
||||
emailSubject = `Please ${recipientActionVerb} your document`;
|
||||
emailMessage = i18n._(
|
||||
msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`,
|
||||
);
|
||||
emailSubject = i18n._(msg`Please ${recipientActionVerb} your document`);
|
||||
}
|
||||
|
||||
if (isDirectTemplate) {
|
||||
emailMessage = `A document was created by your direct template that requires you to ${recipientActionVerb} it.`;
|
||||
emailSubject = `Please ${recipientActionVerb} this document created by your direct template`;
|
||||
emailMessage = i18n._(
|
||||
msg`A document was created by your direct template that requires you to ${recipientActionVerb} it.`,
|
||||
);
|
||||
emailSubject = i18n._(
|
||||
msg`Please ${recipientActionVerb} this document created by your direct template`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isTeamDocument && team) {
|
||||
emailSubject = `${team.name} invited you to ${recipientActionVerb} a document`;
|
||||
emailMessage = `${user.name} on behalf of ${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`;
|
||||
emailSubject = i18n._(msg`${team.name} invited you to ${recipientActionVerb} a document`);
|
||||
emailMessage = i18n._(
|
||||
msg`${user.name} on behalf of ${team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`,
|
||||
);
|
||||
}
|
||||
|
||||
const customEmailTemplate = {
|
||||
@ -132,6 +144,14 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
});
|
||||
|
||||
await io.runTask('send-signing-email', async () => {
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: documentMeta?.language }),
|
||||
renderEmailWithI18N(template, {
|
||||
lang: documentMeta?.language,
|
||||
plainText: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
name: recipient.name,
|
||||
@ -145,8 +165,8 @@ export const SEND_SIGNING_EMAIL_JOB_DEFINITION = {
|
||||
documentMeta?.subject || emailSubject,
|
||||
customEmailTemplate,
|
||||
),
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
|
||||
import { WEBAPP_BASE_URL } from '../../../constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
||||
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION_ID = 'send.team-member-joined.email';
|
||||
@ -71,15 +73,23 @@ export const SEND_TEAM_MEMBER_JOINED_EMAIL_JOB_DEFINITION = {
|
||||
teamUrl: team.url,
|
||||
});
|
||||
|
||||
// !: Replace with the actual language of the recipient later
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(emailContent),
|
||||
renderEmailWithI18N(emailContent, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: member.user.email,
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: 'A new member has joined your team',
|
||||
html: render(emailContent),
|
||||
text: render(emailContent, { plainText: true }),
|
||||
subject: i18n._(msg`A new member has joined your team`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import TeamJoinEmailTemplate from '@documenso/email/templates/team-join';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberRole } from '@documenso/prisma/client';
|
||||
|
||||
import { getI18nInstance } from '../../../client-only/providers/i18n.server';
|
||||
import { WEBAPP_BASE_URL } from '../../../constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '../../../constants/email';
|
||||
import { renderEmailWithI18N } from '../../../utils/render-email-with-i18n';
|
||||
import type { JobDefinition } from '../../client/_internal/job';
|
||||
|
||||
const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION_ID = 'send.team-member-left.email';
|
||||
@ -61,15 +63,22 @@ export const SEND_TEAM_MEMBER_LEFT_EMAIL_JOB_DEFINITION = {
|
||||
teamUrl: team.url,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(emailContent),
|
||||
renderEmailWithI18N(emailContent, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: member.user.email,
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `A team member has left ${team.name}`,
|
||||
html: render(emailContent),
|
||||
text: render(emailContent, { plainText: true }),
|
||||
subject: i18n._(msg`A team member has left ${team.name}`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -25,6 +25,9 @@
|
||||
"@documenso/email": "*",
|
||||
"@documenso/prisma": "*",
|
||||
"@documenso/signing": "*",
|
||||
"@lingui/core": "^4.11.3",
|
||||
"@lingui/macro": "^4.11.3",
|
||||
"@lingui/react": "^4.11.3",
|
||||
"@next-auth/prisma-adapter": "1.0.7",
|
||||
"@noble/ciphers": "0.4.0",
|
||||
"@noble/hashes": "1.3.2",
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { ConfirmEmailTemplate } from '@documenso/email/templates/confirm-email';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendConfirmationEmailProps {
|
||||
userId: number;
|
||||
@ -45,6 +48,13 @@ export const sendConfirmationEmail = async ({ userId }: SendConfirmationEmailPro
|
||||
confirmationLink,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(confirmationTemplate),
|
||||
renderEmailWithI18N(confirmationTemplate, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
return mailer.sendMail({
|
||||
to: {
|
||||
address: user.email,
|
||||
@ -54,8 +64,8 @@ export const sendConfirmationEmail = async ({ userId }: SendConfirmationEmailPro
|
||||
name: senderName,
|
||||
address: senderAddress,
|
||||
},
|
||||
subject: 'Please confirm your email',
|
||||
html: render(confirmationTemplate),
|
||||
text: render(confirmationTemplate, { plainText: true }),
|
||||
subject: i18n._(msg`Please confirm your email`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { ForgotPasswordTemplate } from '@documenso/email/templates/forgot-password';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendForgotPasswordOptions {
|
||||
userId: number;
|
||||
@ -39,6 +42,13 @@ export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions)
|
||||
resetPasswordLink,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
return await mailer.sendMail({
|
||||
to: {
|
||||
address: user.email,
|
||||
@ -48,8 +58,8 @@ export const sendForgotPassword = async ({ userId }: SendForgotPasswordOptions)
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Forgot Password?',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Forgot Password?`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { ResetPasswordTemplate } from '@documenso/email/templates/reset-password';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendResetPasswordOptions {
|
||||
userId: number;
|
||||
@ -26,6 +26,11 @@ export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) =>
|
||||
userName: user.name || '',
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
return await mailer.sendMail({
|
||||
to: {
|
||||
address: user.email,
|
||||
@ -36,7 +41,7 @@ export const sendResetPassword = async ({ userId }: SendResetPasswordOptions) =>
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Password Reset Success!',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -9,6 +9,8 @@ import {
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { DocumentSigningOrder } from '@documenso/prisma/client';
|
||||
|
||||
import type { SupportedLanguageCodes } from '../../constants/i18n';
|
||||
|
||||
export type CreateDocumentMetaOptions = {
|
||||
documentId: number;
|
||||
subject?: string;
|
||||
@ -19,6 +21,7 @@ export type CreateDocumentMetaOptions = {
|
||||
redirectUrl?: string;
|
||||
signingOrder?: DocumentSigningOrder;
|
||||
typedSignatureEnabled?: boolean;
|
||||
language?: SupportedLanguageCodes;
|
||||
userId: number;
|
||||
requestMetadata: RequestMetadata;
|
||||
};
|
||||
@ -34,6 +37,7 @@ export const upsertDocumentMeta = async ({
|
||||
redirectUrl,
|
||||
signingOrder,
|
||||
typedSignatureEnabled,
|
||||
language,
|
||||
requestMetadata,
|
||||
}: CreateDocumentMetaOptions) => {
|
||||
const user = await prisma.user.findFirstOrThrow({
|
||||
@ -85,6 +89,7 @@ export const upsertDocumentMeta = async ({
|
||||
redirectUrl,
|
||||
signingOrder,
|
||||
typedSignatureEnabled,
|
||||
language,
|
||||
},
|
||||
update: {
|
||||
subject,
|
||||
@ -95,6 +100,7 @@ export const upsertDocumentMeta = async ({
|
||||
redirectUrl,
|
||||
signingOrder,
|
||||
typedSignatureEnabled,
|
||||
language,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Document, DocumentMeta, Recipient, User } from '@documenso/prisma/client';
|
||||
@ -14,6 +13,7 @@ import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type DeleteDocumentOptions = {
|
||||
id: number;
|
||||
@ -191,6 +191,11 @@ const handleDocumentOwnerDelete = async ({
|
||||
assetBaseUrl,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: recipient.email,
|
||||
@ -201,8 +206,8 @@ const handleDocumentOwnerDelete = async ({
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: 'Document Cancelled',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||
import {
|
||||
RECIPIENT_ROLES_DESCRIPTION_ENG,
|
||||
RECIPIENT_ROLES_DESCRIPTION,
|
||||
RECIPIENT_ROLE_TO_EMAIL_TYPE,
|
||||
} from '@documenso/lib/constants/recipient-roles';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
@ -16,7 +17,9 @@ import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/client';
|
||||
import type { Prisma } from '@documenso/prisma/client';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
import { getDocumentWhereInput } from './get-document-by-id';
|
||||
|
||||
export type ResendDocumentOptions = {
|
||||
@ -92,25 +95,28 @@ export const resendDocument = async ({
|
||||
return;
|
||||
}
|
||||
|
||||
const i18n = await getI18nInstance(document.documentMeta?.language);
|
||||
|
||||
const recipientEmailType = RECIPIENT_ROLE_TO_EMAIL_TYPE[recipient.role];
|
||||
|
||||
const { email, name } = recipient;
|
||||
const selfSigner = email === user.email;
|
||||
|
||||
const recipientActionVerb =
|
||||
RECIPIENT_ROLES_DESCRIPTION_ENG[recipient.role].actionVerb.toLowerCase();
|
||||
const recipientActionVerb = i18n
|
||||
._(RECIPIENT_ROLES_DESCRIPTION[recipient.role].actionVerb)
|
||||
.toLowerCase();
|
||||
|
||||
let emailMessage = customEmail?.message || '';
|
||||
let emailSubject = `Reminder: Please ${recipientActionVerb} this document`;
|
||||
let emailMessage = msg`${customEmail?.message || ''}`;
|
||||
let emailSubject = msg`Reminder: Please ${recipientActionVerb} this document`;
|
||||
|
||||
if (selfSigner) {
|
||||
emailMessage = `You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`;
|
||||
emailSubject = `Reminder: Please ${recipientActionVerb} your document`;
|
||||
emailMessage = msg`You have initiated the document ${`"${document.title}"`} that requires you to ${recipientActionVerb} it.`;
|
||||
emailSubject = msg`Reminder: Please ${recipientActionVerb} your document`;
|
||||
}
|
||||
|
||||
if (isTeamDocument && document.team) {
|
||||
emailSubject = `Reminder: ${document.team.name} invited you to ${recipientActionVerb} a document`;
|
||||
emailMessage = `${user.name} on behalf of ${document.team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`;
|
||||
emailSubject = msg`Reminder: ${document.team.name} invited you to ${recipientActionVerb} a document`;
|
||||
emailMessage = msg`${user.name} on behalf of ${document.team.name} has invited you to ${recipientActionVerb} the document "${document.title}".`;
|
||||
}
|
||||
|
||||
const customEmailTemplate = {
|
||||
@ -128,7 +134,7 @@ export const resendDocument = async ({
|
||||
inviterEmail: isTeamDocument ? document.team?.teamEmail?.email || user.email : user.email,
|
||||
assetBaseUrl,
|
||||
signDocumentLink,
|
||||
customBody: renderCustomEmailTemplate(emailMessage, customEmailTemplate),
|
||||
customBody: renderCustomEmailTemplate(i18n._(emailMessage), customEmailTemplate),
|
||||
role: recipient.role,
|
||||
selfSigner,
|
||||
isTeamInvite: isTeamDocument,
|
||||
@ -137,6 +143,14 @@ export const resendDocument = async ({
|
||||
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, {
|
||||
lang: document.documentMeta?.language,
|
||||
plainText: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: email,
|
||||
@ -147,10 +161,13 @@ export const resendDocument = async ({
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: customEmail?.subject
|
||||
? renderCustomEmailTemplate(`Reminder: ${customEmail.subject}`, customEmailTemplate)
|
||||
: emailSubject,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
? renderCustomEmailTemplate(
|
||||
i18n._(msg`Reminder: ${customEmail.subject}`),
|
||||
customEmailTemplate,
|
||||
)
|
||||
: i18n._(emailSubject),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
|
||||
await tx.documentAuditLog.create({
|
||||
|
||||
@ -1,17 +1,20 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentCompletedEmailTemplate } from '@documenso/email/templates/document-completed';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentSource } from '@documenso/prisma/client';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { getFile } from '../../universal/upload/get-file';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
import { renderCustomEmailTemplate } from '../../utils/render-custom-email-template';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendDocumentOptions {
|
||||
documentId: number;
|
||||
@ -61,6 +64,8 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
||||
}`;
|
||||
}
|
||||
|
||||
const i18n = await getI18nInstance(document.documentMeta?.language);
|
||||
|
||||
// If the document owner is not a recipient then send the email to them separately
|
||||
if (!document.Recipient.find((recipient) => recipient.email === owner.email)) {
|
||||
const template = createElement(DocumentCompletedEmailTemplate, {
|
||||
@ -69,6 +74,11 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
||||
downloadLink: documentOwnerDownloadLink,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: [
|
||||
{
|
||||
@ -80,9 +90,9 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Signing Complete!',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Signing Complete!`),
|
||||
html,
|
||||
text,
|
||||
attachments: [
|
||||
{
|
||||
filename: document.title.endsWith('.pdf') ? document.title : document.title + '.pdf',
|
||||
@ -129,6 +139,11 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: [
|
||||
{
|
||||
@ -143,9 +158,9 @@ export const sendCompletedEmail = async ({ documentId, requestMetadata }: SendDo
|
||||
subject:
|
||||
isDirectTemplate && document.documentMeta?.subject
|
||||
? renderCustomEmailTemplate(document.documentMeta.subject, customEmailTemplate)
|
||||
: 'Signing Complete!',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
: i18n._(msg`Signing Complete!`),
|
||||
html,
|
||||
text,
|
||||
attachments: [
|
||||
{
|
||||
filename: document.title.endsWith('.pdf') ? document.title : document.title + '.pdf',
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentSuperDeleteEmailTemplate } from '@documenso/email/templates/document-super-delete';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendDeleteEmailOptions {
|
||||
documentId: number;
|
||||
@ -36,6 +39,13 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt
|
||||
assetBaseUrl,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: email,
|
||||
@ -45,8 +55,8 @@ export const sendDeleteEmail = async ({ documentId, reason }: SendDeleteEmailOpt
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Document Deleted!',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Document Deleted!`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentPendingEmailTemplate } from '@documenso/email/templates/document-pending';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SendPendingEmailOptions {
|
||||
documentId: number;
|
||||
@ -28,6 +31,7 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
|
||||
id: recipientId,
|
||||
},
|
||||
},
|
||||
documentMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -50,6 +54,13 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
|
||||
assetBaseUrl,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: email,
|
||||
@ -59,8 +70,8 @@ export const sendPendingEmail = async ({ documentId, recipientId }: SendPendingE
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Waiting for others to complete signing.',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Waiting for others to complete signing.`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -2,17 +2,20 @@
|
||||
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import DocumentCancelTemplate from '@documenso/email/templates/document-cancel';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import type { RequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type SuperDeleteDocumentOptions = {
|
||||
id: number;
|
||||
@ -53,6 +56,13 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
|
||||
assetBaseUrl,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance(document.documentMeta?.language);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: recipient.email,
|
||||
@ -62,9 +72,9 @@ export const superDeleteDocument = async ({ id, requestMetadata }: SuperDeleteDo
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: 'Document Cancelled',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Document Cancelled`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import RecipientRemovedFromDocumentTemplate from '@documenso/email/templates/recipient-removed-from-document';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import {
|
||||
@ -21,10 +22,12 @@ import type { Recipient } from '@documenso/prisma/client';
|
||||
import { RecipientRole } from '@documenso/prisma/client';
|
||||
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { canRecipientBeModified } from '../../utils/recipients';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export interface SetRecipientsForDocumentOptions {
|
||||
userId: number;
|
||||
@ -62,6 +65,7 @@ export const setRecipientsForDocument = async ({
|
||||
},
|
||||
include: {
|
||||
Field: true,
|
||||
documentMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -291,6 +295,13 @@ export const setRecipientsForDocument = async ({
|
||||
assetBaseUrl,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language }),
|
||||
renderEmailWithI18N(template, { lang: document.documentMeta?.language, plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance(document.documentMeta?.language);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: recipient.email,
|
||||
@ -300,9 +311,9 @@ export const setRecipientsForDocument = async ({
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: 'You have been removed from a document',
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`You have been removed from a document`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
@ -81,7 +81,7 @@ export const acceptTeamInvitation = async ({ userId, teamId }: AcceptTeamInvitat
|
||||
await jobs.triggerJob({
|
||||
name: 'send.team-member-joined.email',
|
||||
payload: {
|
||||
teamId: team.id,
|
||||
teamId: teamMember.teamId,
|
||||
memberId: teamMember.id,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { ConfirmTeamEmailTemplate } from '@documenso/email/templates/confirm-team-email';
|
||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||
@ -13,6 +13,9 @@ import { createTokenVerification } from '@documenso/lib/utils/token-verification
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { Prisma } from '@documenso/prisma/client';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type CreateTeamEmailVerificationOptions = {
|
||||
userId: number;
|
||||
teamId: number;
|
||||
@ -122,14 +125,23 @@ export const sendTeamEmailVerificationEmail = async (
|
||||
token,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: email,
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `A request to use your email has been initiated by ${teamName} on Documenso`,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(
|
||||
msg`A request to use your email has been initiated by ${teamName} on Documenso`,
|
||||
),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import type { TeamInviteEmailProps } from '@documenso/email/templates/team-invite';
|
||||
import { TeamInviteEmailTemplate } from '@documenso/email/templates/team-invite';
|
||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
@ -15,6 +15,9 @@ import { prisma } from '@documenso/prisma';
|
||||
import { TeamMemberInviteStatus } from '@documenso/prisma/client';
|
||||
import type { TCreateTeamMemberInvitesMutationSchema } from '@documenso/trpc/server/team-router/schema';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type CreateTeamMemberInvitesOptions = {
|
||||
userId: number;
|
||||
userName: string;
|
||||
@ -148,14 +151,23 @@ export const sendTeamMemberInviteEmail = async ({
|
||||
...emailTemplateOptions,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: email,
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `You have been invited to join ${emailTemplateOptions.teamName} on Documenso`,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(
|
||||
msg`You have been invited to join ${emailTemplateOptions.teamName} on Documenso`,
|
||||
),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { TeamEmailRemovedTemplate } from '@documenso/email/templates/team-email-removed';
|
||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type DeleteTeamEmailOptions = {
|
||||
userId: number;
|
||||
userEmail: string;
|
||||
@ -73,6 +77,13 @@ export const deleteTeamEmail = async ({ userId, userEmail, teamId }: DeleteTeamE
|
||||
teamUrl: team.url,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: {
|
||||
address: team.owner.email,
|
||||
@ -82,9 +93,9 @@ export const deleteTeamEmail = async ({ userId, userEmail, teamId }: DeleteTeamE
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `Team email has been revoked for ${team.name}`,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(msg`Team email has been revoked for ${team.name}`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
} catch (e) {
|
||||
// Todo: Teams - Alert us.
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import type { TeamDeleteEmailProps } from '@documenso/email/templates/team-delete';
|
||||
import { TeamDeleteEmailTemplate } from '@documenso/email/templates/team-delete';
|
||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
@ -11,6 +10,7 @@ import { stripe } from '@documenso/lib/server-only/stripe';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { jobs } from '../../jobs/client';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type DeleteTeamOptions = {
|
||||
userId: number;
|
||||
@ -95,6 +95,11 @@ export const sendTeamDeleteEmail = async ({
|
||||
...emailTemplateOptions,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: email,
|
||||
from: {
|
||||
@ -102,7 +107,7 @@ export const sendTeamDeleteEmail = async ({
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `Team "${emailTemplateOptions.teamName}" has been deleted on Documenso`,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { TeamTransferRequestTemplate } from '@documenso/email/templates/team-transfer-request';
|
||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
||||
import { createTokenVerification } from '@documenso/lib/utils/token-verification';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
|
||||
export type RequestTeamOwnershipTransferOptions = {
|
||||
/**
|
||||
* The ID of the user initiating the transfer.
|
||||
@ -93,15 +97,24 @@ export const requestTeamOwnershipTransfer = async ({
|
||||
token,
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(template),
|
||||
renderEmailWithI18N(template, { plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance();
|
||||
|
||||
await mailer.sendMail({
|
||||
to: newOwnerUser.email,
|
||||
from: {
|
||||
name: FROM_NAME,
|
||||
address: FROM_ADDRESS,
|
||||
},
|
||||
subject: `You have been requested to take ownership of team ${team.name} on Documenso`,
|
||||
html: render(template),
|
||||
text: render(template, { plainText: true }),
|
||||
subject: i18n._(
|
||||
msg`You have been requested to take ownership of team ${team.name} on Documenso`,
|
||||
),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
},
|
||||
{ timeout: 30_000 },
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { createElement } from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { DateTime } from 'luxon';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { mailer } from '@documenso/email/mailer';
|
||||
import { render } from '@documenso/email/render';
|
||||
import { DocumentCreatedFromDirectTemplateEmailTemplate } from '@documenso/email/templates/document-created-from-direct-template';
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Field, Signature } from '@documenso/prisma/client';
|
||||
import {
|
||||
DocumentSigningOrder,
|
||||
DocumentSource,
|
||||
DocumentStatus,
|
||||
FieldType,
|
||||
@ -21,6 +22,7 @@ import {
|
||||
} from '@documenso/prisma/client';
|
||||
import type { TSignFieldWithTokenMutationSchema } from '@documenso/trpc/server/field-router/schema';
|
||||
|
||||
import { getI18nInstance } from '../../client-only/providers/i18n.server';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '../../constants/date-formats';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE } from '../../constants/time-zones';
|
||||
@ -37,6 +39,7 @@ import {
|
||||
createRecipientAuthOptions,
|
||||
extractDocumentAuthMethods,
|
||||
} from '../../utils/document-auth';
|
||||
import { renderEmailWithI18N } from '../../utils/render-email-with-i18n';
|
||||
import { formatDocumentsPath } from '../../utils/teams';
|
||||
import { sendDocument } from '../document/send-document';
|
||||
import { validateFieldAuth } from '../document/validate-field-auth';
|
||||
@ -142,6 +145,8 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
const metaDateFormat = template.templateMeta?.dateFormat || DEFAULT_DOCUMENT_DATE_FORMAT;
|
||||
const metaEmailMessage = template.templateMeta?.message || '';
|
||||
const metaEmailSubject = template.templateMeta?.subject || '';
|
||||
const metaLanguage = template.templateMeta?.language;
|
||||
const metaSigningOrder = template.templateMeta?.signingOrder || DocumentSigningOrder.PARALLEL;
|
||||
|
||||
// Associate, validate and map to a query every direct template recipient field with the provided fields.
|
||||
const createDirectRecipientFieldArgs = await Promise.all(
|
||||
@ -256,6 +261,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
recipient.role === RecipientRole.CC
|
||||
? SigningStatus.SIGNED
|
||||
: SigningStatus.NOT_SIGNED,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
};
|
||||
}),
|
||||
@ -267,6 +273,8 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
dateFormat: metaDateFormat,
|
||||
message: metaEmailMessage,
|
||||
subject: metaEmailSubject,
|
||||
language: metaLanguage,
|
||||
signingOrder: metaSigningOrder,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -330,6 +338,7 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
sendStatus: SendStatus.SENT,
|
||||
signedAt: initialRequestTime,
|
||||
signingOrder: directTemplateRecipient.signingOrder,
|
||||
Field: {
|
||||
createMany: {
|
||||
data: directTemplateNonSignatureFields.map(({ templateField, customText }) => ({
|
||||
@ -524,6 +533,13 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
assetBaseUrl: NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000',
|
||||
});
|
||||
|
||||
const [html, text] = await Promise.all([
|
||||
renderEmailWithI18N(emailTemplate, { lang: metaLanguage }),
|
||||
renderEmailWithI18N(emailTemplate, { lang: metaLanguage, plainText: true }),
|
||||
]);
|
||||
|
||||
const i18n = await getI18nInstance(metaLanguage);
|
||||
|
||||
await mailer.sendMail({
|
||||
to: [
|
||||
{
|
||||
@ -535,9 +551,9 @@ export const createDocumentFromDirectTemplate = async ({
|
||||
name: process.env.NEXT_PRIVATE_SMTP_FROM_NAME || 'Documenso',
|
||||
address: process.env.NEXT_PRIVATE_SMTP_FROM_ADDRESS || 'noreply@documenso.com',
|
||||
},
|
||||
subject: 'Document created from direct template',
|
||||
html: render(emailTemplate),
|
||||
text: render(emailTemplate, { plainText: true }),
|
||||
subject: i18n._(msg`Document created from direct template`),
|
||||
html,
|
||||
text,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@ -46,6 +46,7 @@ export const createDocumentFromTemplateLegacy = async ({
|
||||
Recipient: true,
|
||||
Field: true,
|
||||
templateDocumentData: true,
|
||||
templateMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -78,6 +79,17 @@ export const createDocumentFromTemplateLegacy = async ({
|
||||
token: nanoid(),
|
||||
})),
|
||||
},
|
||||
documentMeta: {
|
||||
create: {
|
||||
subject: template.templateMeta?.subject,
|
||||
message: template.templateMeta?.message,
|
||||
timezone: template.templateMeta?.timezone,
|
||||
dateFormat: template.templateMeta?.dateFormat,
|
||||
redirectUrl: template.templateMeta?.redirectUrl,
|
||||
signingOrder: template.templateMeta?.signingOrder ?? undefined,
|
||||
language: template.templateMeta?.language,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
include: {
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
WebhookTriggerEvents,
|
||||
} from '@documenso/prisma/client';
|
||||
|
||||
import type { SupportedLanguageCodes } from '../../constants/i18n';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import { ZRecipientAuthOptionsSchema } from '../../types/document-auth';
|
||||
@ -24,7 +25,10 @@ import {
|
||||
} from '../../utils/document-auth';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
|
||||
type FinalRecipient = Pick<Recipient, 'name' | 'email' | 'role' | 'authOptions'> & {
|
||||
type FinalRecipient = Pick<
|
||||
Recipient,
|
||||
'name' | 'email' | 'role' | 'authOptions' | 'signingOrder'
|
||||
> & {
|
||||
templateRecipientId: number;
|
||||
fields: Field[];
|
||||
};
|
||||
@ -57,6 +61,7 @@ export type CreateDocumentFromTemplateOptions = {
|
||||
dateFormat?: string;
|
||||
redirectUrl?: string;
|
||||
signingOrder?: DocumentSigningOrder;
|
||||
language?: SupportedLanguageCodes;
|
||||
};
|
||||
requestMetadata?: RequestMetadata;
|
||||
};
|
||||
@ -176,6 +181,7 @@ export const createDocumentFromTemplate = async ({
|
||||
override?.signingOrder ||
|
||||
template.templateMeta?.signingOrder ||
|
||||
DocumentSigningOrder.PARALLEL,
|
||||
language: override?.language || template.templateMeta?.language,
|
||||
},
|
||||
},
|
||||
Recipient: {
|
||||
@ -197,6 +203,7 @@ export const createDocumentFromTemplate = async ({
|
||||
recipient.role === RecipientRole.CC
|
||||
? SigningStatus.SIGNED
|
||||
: SigningStatus.NOT_SIGNED,
|
||||
signingOrder: recipient.signingOrder,
|
||||
token: nanoid(),
|
||||
};
|
||||
}),
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { omit } from 'remeda';
|
||||
|
||||
import { nanoid } from '@documenso/lib/universal/id';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import type { Prisma } from '@documenso/prisma/client';
|
||||
@ -38,6 +40,7 @@ export const duplicateTemplate = async ({
|
||||
Recipient: true,
|
||||
Field: true,
|
||||
templateDocumentData: true,
|
||||
templateMeta: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -53,6 +56,14 @@ export const duplicateTemplate = async ({
|
||||
},
|
||||
});
|
||||
|
||||
let templateMeta: Prisma.TemplateCreateArgs['data']['templateMeta'] | undefined = undefined;
|
||||
|
||||
if (template.templateMeta) {
|
||||
templateMeta = {
|
||||
create: omit(template.templateMeta, ['id', 'templateId']),
|
||||
};
|
||||
}
|
||||
|
||||
const duplicatedTemplate = await prisma.template.create({
|
||||
data: {
|
||||
userId,
|
||||
@ -66,8 +77,8 @@ export const duplicateTemplate = async ({
|
||||
token: nanoid(),
|
||||
})),
|
||||
},
|
||||
templateMeta,
|
||||
},
|
||||
|
||||
include: {
|
||||
Recipient: true,
|
||||
},
|
||||
|
||||
@ -39,7 +39,7 @@ export const sendConfirmationToken = async ({
|
||||
mostRecentToken?.createdAt &&
|
||||
DateTime.fromJSDate(mostRecentToken.createdAt).diffNow('minutes').minutes > -5
|
||||
) {
|
||||
return;
|
||||
// return;
|
||||
}
|
||||
|
||||
const createdToken = await prisma.verificationToken.create({
|
||||
@ -64,6 +64,7 @@ export const sendConfirmationToken = async ({
|
||||
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
throw new Error(`Failed to send the confirmation email`);
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-10-18 04:04\n"
|
||||
"PO-Revision-Date: 2024-11-01 02:29\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@ -42,7 +42,7 @@ msgstr "Dokument hinzufügen"
|
||||
msgid "Add More Users for {0}"
|
||||
msgstr "Mehr Benutzer hinzufügen für {0}"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:165
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:164
|
||||
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||
msgstr "Alle unsere Kennzahlen, Finanzen und Erkenntnisse sind öffentlich. Wir glauben an Transparenz und möchten unsere Reise mit Ihnen teilen. Mehr erfahren Sie hier: <0>Ankündigung Offene Kennzahlen</0>"
|
||||
|
||||
@ -90,7 +90,7 @@ msgstr "Änderungsprotokoll"
|
||||
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
msgstr "Wählen Sie eine Vorlage aus dem Community-App-Store. Oder reichen Sie Ihre eigene Vorlage ein, damit andere sie benutzen können."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:219
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:218
|
||||
msgid "Community"
|
||||
msgstr "Gemeinschaft"
|
||||
|
||||
@ -160,10 +160,6 @@ msgstr "Dokumentation"
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Betten Sie Documenso ganz einfach in Ihr Produkt ein. Kopieren und fügen Sie einfach unser React-Widget in Ihre Anwendung ein."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Einfaches Teilen."
|
||||
@ -197,7 +193,7 @@ msgstr "Schnell."
|
||||
msgid "Faster, smarter and more beautiful."
|
||||
msgstr "Schneller, intelligenter und schöner."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:210
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:209
|
||||
msgid "Finances"
|
||||
msgstr "Finanzen"
|
||||
|
||||
@ -250,15 +246,15 @@ msgstr "Fangen Sie heute an."
|
||||
msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||
msgstr "Erhalten Sie die neuesten Nachrichten von Documenso, einschließlich Produkt-Updates, Team-Ankündigungen und mehr!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:232
|
||||
msgid "GitHub: Total Merged PRs"
|
||||
msgstr "GitHub: Gesamte PRs zusammengeführt"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:250
|
||||
msgid "GitHub: Total Open Issues"
|
||||
msgstr "GitHub: Gesamte offene Issues"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:224
|
||||
msgid "GitHub: Total Stars"
|
||||
msgstr "GitHub: Gesamtanzahl Sterne"
|
||||
|
||||
@ -266,7 +262,7 @@ msgstr "GitHub: Gesamtanzahl Sterne"
|
||||
msgid "Global Salary Bands"
|
||||
msgstr "Globale Gehaltsbänder"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:261
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:260
|
||||
msgid "Growth"
|
||||
msgstr "Wachstum"
|
||||
|
||||
@ -290,7 +286,7 @@ msgstr "Integrierte Zahlungen mit Stripe, sodass Sie sich keine Sorgen ums Bezah
|
||||
msgid "Integrates with all your favourite tools."
|
||||
msgstr "Integriert sich mit all Ihren Lieblingstools."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:289
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:288
|
||||
msgid "Is there more?"
|
||||
msgstr "Gibt es mehr?"
|
||||
|
||||
@ -314,11 +310,11 @@ msgstr "Standort"
|
||||
msgid "Make it your own through advanced customization and adjustability."
|
||||
msgstr "Machen Sie es zu Ihrem eigenen durch erweiterte Anpassung und Einstellbarkeit."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:199
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:198
|
||||
msgid "Merged PR's"
|
||||
msgstr "Zusammengeführte PRs"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:234
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
msgid "Merged PRs"
|
||||
msgstr "Zusammengeführte PRs"
|
||||
|
||||
@ -349,8 +345,8 @@ msgstr "Keine Kreditkarte erforderlich"
|
||||
msgid "None of these work for you? Try self-hosting!"
|
||||
msgstr "Keines dieser Angebote passt zu Ihnen? Versuchen Sie das Selbst-Hosting!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:194
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:252
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:193
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
msgid "Open Issues"
|
||||
msgstr "Offene Issues"
|
||||
|
||||
@ -358,7 +354,7 @@ msgstr "Offene Issues"
|
||||
msgid "Open Source or Hosted."
|
||||
msgstr "Open Source oder Hosted."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:161
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:160
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:37
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:64
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
|
||||
@ -377,18 +373,10 @@ msgstr "Unsere benutzerdefinierten Vorlagen verfügen über intelligente Regeln,
|
||||
msgid "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||
msgstr "Unsere Enterprise-Lizenz ist ideal für große Organisationen, die auf Documenso für all ihre Signaturanforderungen umsteigen möchten. Sie ist sowohl für unser Cloud-Angebot als auch für selbstgehostete Setups verfügbar und bietet eine breite Palette an Compliance- und Verwaltungsfunktionen."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||
#~ msgid "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
#~ msgstr "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:65
|
||||
msgid "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||
msgstr "Unsere selbstgehostete Option ist ideal für kleine Teams und Einzelpersonen, die eine einfache Lösung benötigen. Sie können unser docker-basiertes Setup verwenden, um in wenigen Minuten loszulegen. Übernehmen Sie die Kontrolle mit vollständiger Anpassbarkeit und Datenhoheit."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||
#~ msgid "Part-Time"
|
||||
#~ msgstr "Part-Time"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||
msgid "Premium Profile Name"
|
||||
msgstr "Premium Profilname"
|
||||
@ -430,10 +418,6 @@ msgstr "Gehalt"
|
||||
msgid "Save $60 or $120"
|
||||
msgstr "Sparen Sie $60 oder $120"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||
#~ msgid "Search languages..."
|
||||
#~ msgstr "Search languages..."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "Sicher. Unsere Rechenzentren befinden sich in Frankfurt (Deutschland) und bieten uns die besten lokalen Datenschutzgesetze. Uns ist die sensible Natur unserer Daten sehr bewusst und wir folgen bewährten Praktiken, um die Sicherheit und Integrität der uns anvertrauten Daten zu gewährleisten."
|
||||
@ -482,7 +466,7 @@ msgstr "Intelligent."
|
||||
msgid "Star on GitHub"
|
||||
msgstr "Auf GitHub favorisieren"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:226
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
msgid "Stars"
|
||||
msgstr "Favoriten"
|
||||
|
||||
@ -517,7 +501,7 @@ msgstr "Vorlagen-Shop (Demnächst)."
|
||||
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
msgstr "Das ist großartig. Sie können sich die aktuellen <0>Issues</0> ansehen und unserer <1>Discord-Community</1> beitreten, um auf dem neuesten Stand zu bleiben, was die aktuellen Prioritäten sind. In jedem Fall sind wir eine offene Gemeinschaft und begrüßen jegliche Beiträge, technische und nicht-technische ❤️"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:293
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:292
|
||||
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||
msgstr "Diese Seite entwickelt sich weiter, während wir lernen, was ein großartiges Signing-Unternehmen ausmacht. Wir werden sie aktualisieren, wenn wir mehr zu teilen haben."
|
||||
|
||||
@ -530,8 +514,8 @@ msgstr "Titel"
|
||||
msgid "Total Completed Documents"
|
||||
msgstr "Insgesamt Abgeschlossene Dokumente"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:267
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
|
||||
msgid "Total Customers"
|
||||
msgstr "Insgesamt Kunden"
|
||||
|
||||
@ -618,4 +602,3 @@ msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatz
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Ihr Browser unterstützt das Video-Tag nicht."
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -37,7 +37,7 @@ msgstr "Add document"
|
||||
msgid "Add More Users for {0}"
|
||||
msgstr "Add More Users for {0}"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:165
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:164
|
||||
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||
msgstr "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||
|
||||
@ -85,7 +85,7 @@ msgstr "Changelog"
|
||||
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
msgstr "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:219
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:218
|
||||
msgid "Community"
|
||||
msgstr "Community"
|
||||
|
||||
@ -155,10 +155,6 @@ msgstr "Documentation"
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Easy Sharing."
|
||||
@ -192,7 +188,7 @@ msgstr "Fast."
|
||||
msgid "Faster, smarter and more beautiful."
|
||||
msgstr "Faster, smarter and more beautiful."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:210
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:209
|
||||
msgid "Finances"
|
||||
msgstr "Finances"
|
||||
|
||||
@ -245,15 +241,15 @@ msgstr "Get started today."
|
||||
msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||
msgstr "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:232
|
||||
msgid "GitHub: Total Merged PRs"
|
||||
msgstr "GitHub: Total Merged PRs"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:250
|
||||
msgid "GitHub: Total Open Issues"
|
||||
msgstr "GitHub: Total Open Issues"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:224
|
||||
msgid "GitHub: Total Stars"
|
||||
msgstr "GitHub: Total Stars"
|
||||
|
||||
@ -261,7 +257,7 @@ msgstr "GitHub: Total Stars"
|
||||
msgid "Global Salary Bands"
|
||||
msgstr "Global Salary Bands"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:261
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:260
|
||||
msgid "Growth"
|
||||
msgstr "Growth"
|
||||
|
||||
@ -285,7 +281,7 @@ msgstr "Integrated payments with Stripe so you don’t have to worry about getti
|
||||
msgid "Integrates with all your favourite tools."
|
||||
msgstr "Integrates with all your favourite tools."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:289
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:288
|
||||
msgid "Is there more?"
|
||||
msgstr "Is there more?"
|
||||
|
||||
@ -309,11 +305,11 @@ msgstr "Location"
|
||||
msgid "Make it your own through advanced customization and adjustability."
|
||||
msgstr "Make it your own through advanced customization and adjustability."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:199
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:198
|
||||
msgid "Merged PR's"
|
||||
msgstr "Merged PR's"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:234
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
msgid "Merged PRs"
|
||||
msgstr "Merged PRs"
|
||||
|
||||
@ -344,8 +340,8 @@ msgstr "No Credit Card required"
|
||||
msgid "None of these work for you? Try self-hosting!"
|
||||
msgstr "None of these work for you? Try self-hosting!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:194
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:252
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:193
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
msgid "Open Issues"
|
||||
msgstr "Open Issues"
|
||||
|
||||
@ -353,7 +349,7 @@ msgstr "Open Issues"
|
||||
msgid "Open Source or Hosted."
|
||||
msgstr "Open Source or Hosted."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:161
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:160
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:37
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:64
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
|
||||
@ -372,18 +368,10 @@ msgstr "Our custom templates come with smart rules that can help you save time a
|
||||
msgid "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||
msgstr "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||
#~ msgid "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
#~ msgstr "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:65
|
||||
msgid "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||
msgstr "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||
#~ msgid "Part-Time"
|
||||
#~ msgstr "Part-Time"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||
msgid "Premium Profile Name"
|
||||
msgstr "Premium Profile Name"
|
||||
@ -425,10 +413,6 @@ msgstr "Salary"
|
||||
msgid "Save $60 or $120"
|
||||
msgstr "Save $60 or $120"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||
#~ msgid "Search languages..."
|
||||
#~ msgstr "Search languages..."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
@ -477,7 +461,7 @@ msgstr "Smart."
|
||||
msgid "Star on GitHub"
|
||||
msgstr "Star on GitHub"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:226
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
msgid "Stars"
|
||||
msgstr "Stars"
|
||||
|
||||
@ -512,7 +496,7 @@ msgstr "Template Store (Soon)."
|
||||
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
msgstr "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:293
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:292
|
||||
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||
msgstr "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||
|
||||
@ -525,8 +509,8 @@ msgstr "Title"
|
||||
msgid "Total Completed Documents"
|
||||
msgstr "Total Completed Documents"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:267
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
|
||||
msgid "Total Customers"
|
||||
msgstr "Total Customers"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: es\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-10-22 02:25\n"
|
||||
"PO-Revision-Date: 2024-11-01 02:29\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@ -42,7 +42,7 @@ msgstr "Agregar documento"
|
||||
msgid "Add More Users for {0}"
|
||||
msgstr "Agregar más usuarios por {0}"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:165
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:164
|
||||
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||
msgstr "Todos nuestros métricas, finanzas y aprendizajes son públicos. Creemos en la transparencia y queremos compartir nuestro viaje contigo. Puedes leer más sobre por qué aquí: <0>Anunciando Métricas Abiertas</0>"
|
||||
|
||||
@ -90,7 +90,7 @@ msgstr "Registro de cambios"
|
||||
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
msgstr "Elige una plantilla de la tienda de aplicaciones de la comunidad. O envía tu propia plantilla para que otros la usen."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:219
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:218
|
||||
msgid "Community"
|
||||
msgstr "Comunidad"
|
||||
|
||||
@ -160,10 +160,6 @@ msgstr "Documentación"
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Incrusta fácilmente Documenso en tu producto. Simplemente copia y pega nuestro widget de react en tu aplicación."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Compartición fácil."
|
||||
@ -197,7 +193,7 @@ msgstr "Rápido."
|
||||
msgid "Faster, smarter and more beautiful."
|
||||
msgstr "Más rápido, más inteligente y más hermoso."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:210
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:209
|
||||
msgid "Finances"
|
||||
msgstr "Finanzas"
|
||||
|
||||
@ -250,15 +246,15 @@ msgstr "Comienza hoy."
|
||||
msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||
msgstr "¡Obtén las últimas noticias de Documenso, incluidas actualizaciones de productos, anuncios del equipo y más!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:232
|
||||
msgid "GitHub: Total Merged PRs"
|
||||
msgstr "GitHub: Total de PRs fusionados"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:250
|
||||
msgid "GitHub: Total Open Issues"
|
||||
msgstr "GitHub: Total de problemas abiertos"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:224
|
||||
msgid "GitHub: Total Stars"
|
||||
msgstr "GitHub: Total de estrellas"
|
||||
|
||||
@ -266,7 +262,7 @@ msgstr "GitHub: Total de estrellas"
|
||||
msgid "Global Salary Bands"
|
||||
msgstr "Bandas salariales globales"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:261
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:260
|
||||
msgid "Growth"
|
||||
msgstr "Crecimiento"
|
||||
|
||||
@ -290,7 +286,7 @@ msgstr "Pagos integrados con Stripe para que no tengas que preocuparte por cobra
|
||||
msgid "Integrates with all your favourite tools."
|
||||
msgstr "Se integra con todas tus herramientas favoritas."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:289
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:288
|
||||
msgid "Is there more?"
|
||||
msgstr "¿Hay más?"
|
||||
|
||||
@ -314,11 +310,11 @@ msgstr "Ubicación"
|
||||
msgid "Make it your own through advanced customization and adjustability."
|
||||
msgstr "Hazlo tuyo a través de personalización y ajustabilidad avanzadas."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:199
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:198
|
||||
msgid "Merged PR's"
|
||||
msgstr "PRs fusionados"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:234
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
msgid "Merged PRs"
|
||||
msgstr "PRs fusionados"
|
||||
|
||||
@ -349,8 +345,8 @@ msgstr "No se requiere tarjeta de crédito"
|
||||
msgid "None of these work for you? Try self-hosting!"
|
||||
msgstr "¿Ninguna de estas opciones funciona para ti? ¡Prueba el autoalojamiento!"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:194
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:252
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:193
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
msgid "Open Issues"
|
||||
msgstr "Problemas Abiertos"
|
||||
|
||||
@ -358,7 +354,7 @@ msgstr "Problemas Abiertos"
|
||||
msgid "Open Source or Hosted."
|
||||
msgstr "Código Abierto o Alojado."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:161
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:160
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:37
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:64
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
|
||||
@ -377,18 +373,10 @@ msgstr "Nuestras plantillas personalizadas vienen con reglas inteligentes que pu
|
||||
msgid "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||
msgstr "Nuestra Licencia Empresarial es excelente para grandes organizaciones que buscan cambiar a Documenso para todas sus necesidades de firma. Está disponible para nuestra oferta en la nube, así como para configuraciones autoalojadas y ofrece una amplia gama de funciones de cumplimiento y administración."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||
#~ msgid "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
#~ msgstr "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:65
|
||||
msgid "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||
msgstr "Nuestra opción de autoalojamiento es excelente para pequeños equipos e individuos que necesitan una solución simple. Puedes usar nuestra configuración basada en docker para empezar en minutos. Toma el control con total personalización y propiedad de los datos."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||
#~ msgid "Part-Time"
|
||||
#~ msgstr "Part-Time"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||
msgid "Premium Profile Name"
|
||||
msgstr "Nombre de Perfil Premium"
|
||||
@ -430,10 +418,6 @@ msgstr "Salario"
|
||||
msgid "Save $60 or $120"
|
||||
msgstr "Ahorra $60 o $120"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||
#~ msgid "Search languages..."
|
||||
#~ msgstr "Search languages..."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "De manera segura. Nuestros centros de datos están ubicados en Frankfurt (Alemania), dándonos las mejores leyes de privacidad locales. Somos muy conscientes de la naturaleza sensible de nuestros datos y seguimos las mejores prácticas para garantizar la seguridad y la integridad de los datos que se nos confían."
|
||||
@ -482,7 +466,7 @@ msgstr "Inteligente."
|
||||
msgid "Star on GitHub"
|
||||
msgstr "Estrella en GitHub"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:226
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
msgid "Stars"
|
||||
msgstr "Estrellas"
|
||||
|
||||
@ -517,7 +501,7 @@ msgstr "Tienda de Plantillas (Pronto)."
|
||||
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
msgstr "Eso es increíble. Puedes echar un vistazo a los <0>Problemas</0> actuales y unirte a nuestra <1>Comunidad de Discord</1> para mantenerte al día sobre cuáles son las prioridades actuales. En cualquier caso, somos una comunidad abierta y agradecemos todas las aportaciones, técnicas y no técnicas ❤️"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:293
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:292
|
||||
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||
msgstr "Esta página está evolucionando a medida que aprendemos lo que hace una gran empresa de firmas. La actualizaremos cuando tengamos más para compartir."
|
||||
|
||||
@ -530,8 +514,8 @@ msgstr "Título"
|
||||
msgid "Total Completed Documents"
|
||||
msgstr "Total de Documentos Completados"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:267
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
|
||||
msgid "Total Customers"
|
||||
msgstr "Total de Clientes"
|
||||
|
||||
@ -618,4 +602,3 @@ msgstr "Puedes autoalojar Documenso de forma gratuita o usar nuestra versión al
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Tu navegador no soporta la etiqueta de video."
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: fr\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2024-10-18 04:04\n"
|
||||
"PO-Revision-Date: 2024-11-01 02:29\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
@ -42,7 +42,7 @@ msgstr "Ajouter un document"
|
||||
msgid "Add More Users for {0}"
|
||||
msgstr "Ajouter plus d'utilisateurs pour {0}"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:165
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:164
|
||||
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||
msgstr "Tous nos indicateurs, finances et apprentissages sont publics. Nous croyons en la transparence et souhaitons partager notre parcours avec vous. Vous pouvez en lire plus sur pourquoi ici : <0>Annonce de Open Metrics</0>"
|
||||
|
||||
@ -90,7 +90,7 @@ msgstr "Changelog"
|
||||
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||
msgstr "Choisissez un modèle dans la boutique d'applications communautaires. Ou soumettez votre propre modèle pour que d'autres puissent l'utiliser."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:219
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:218
|
||||
msgid "Community"
|
||||
msgstr "Communauté"
|
||||
|
||||
@ -160,10 +160,6 @@ msgstr "Documentation"
|
||||
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||
msgstr "Intégrez facilement Documenso dans votre produit. Il vous suffit de copier et coller notre widget React dans votre application."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||
#~ msgid "Easy Sharing (Soon)."
|
||||
#~ msgstr "Easy Sharing (Soon)."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||
msgid "Easy Sharing."
|
||||
msgstr "Partage facile."
|
||||
@ -197,7 +193,7 @@ msgstr "Rapide."
|
||||
msgid "Faster, smarter and more beautiful."
|
||||
msgstr "Plus rapide, plus intelligent et plus beau."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:210
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:209
|
||||
msgid "Finances"
|
||||
msgstr "Finances"
|
||||
|
||||
@ -250,15 +246,15 @@ msgstr "Commencez aujourd'hui."
|
||||
msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||
msgstr "Obtenez les dernières nouvelles de Documenso, y compris les mises à jour de produits, les annonces d'équipe et plus encore !"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:232
|
||||
msgid "GitHub: Total Merged PRs"
|
||||
msgstr "GitHub : Total des PRs fusionnées"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:250
|
||||
msgid "GitHub: Total Open Issues"
|
||||
msgstr "GitHub : Total des problèmes ouverts"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:224
|
||||
msgid "GitHub: Total Stars"
|
||||
msgstr "GitHub : Nombre total d'étoiles"
|
||||
|
||||
@ -266,7 +262,7 @@ msgstr "GitHub : Nombre total d'étoiles"
|
||||
msgid "Global Salary Bands"
|
||||
msgstr "Bandes de salaire globales"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:261
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:260
|
||||
msgid "Growth"
|
||||
msgstr "Croissance"
|
||||
|
||||
@ -290,7 +286,7 @@ msgstr "Paiements intégrés avec Stripe afin que vous n'ayez pas à vous soucie
|
||||
msgid "Integrates with all your favourite tools."
|
||||
msgstr "S'intègre à tous vos outils préférés."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:289
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:288
|
||||
msgid "Is there more?"
|
||||
msgstr "Y a-t-il plus ?"
|
||||
|
||||
@ -314,11 +310,11 @@ msgstr "Emplacement"
|
||||
msgid "Make it your own through advanced customization and adjustability."
|
||||
msgstr "Faites-en votre propre grâce à une personnalisation avancée et un ajustement."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:199
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:198
|
||||
msgid "Merged PR's"
|
||||
msgstr "PRs fusionnées"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:234
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||
msgid "Merged PRs"
|
||||
msgstr "PRs fusionnées"
|
||||
|
||||
@ -349,8 +345,8 @@ msgstr "Aucune carte de crédit requise"
|
||||
msgid "None of these work for you? Try self-hosting!"
|
||||
msgstr "Aucune de ces options ne fonctionne pour vous ? Essayez l'hébergement autonome !"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:194
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:252
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:193
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||
msgid "Open Issues"
|
||||
msgstr "Problèmes ouverts"
|
||||
|
||||
@ -358,7 +354,7 @@ msgstr "Problèmes ouverts"
|
||||
msgid "Open Source or Hosted."
|
||||
msgstr "Open Source ou hébergé."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:161
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:160
|
||||
#: apps/marketing/src/components/(marketing)/footer.tsx:37
|
||||
#: apps/marketing/src/components/(marketing)/header.tsx:64
|
||||
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
|
||||
@ -377,18 +373,10 @@ msgstr "Nos modèles personnalisés sont dotés de règles intelligentes qui peu
|
||||
msgid "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||
msgstr "Notre licence entreprise est idéale pour les grandes organisations cherchant à passer à Documenso pour tous leurs besoins de signature. Elle est disponible pour notre offre cloud ainsi que pour des configurations auto-hébergées et propose un large éventail de fonctionnalités de conformité et d'administration."
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||
#~ msgid "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
#~ msgstr "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:65
|
||||
msgid "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||
msgstr "Notre option auto-hébergée est idéale pour les petites équipes et les individus qui ont besoin d'une solution simple. Vous pouvez utiliser notre configuration basée sur Docker pour commencer en quelques minutes. Prenez le contrôle avec une personnalisation complète et une propriété des données."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||
#~ msgid "Part-Time"
|
||||
#~ msgstr "Part-Time"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||
msgid "Premium Profile Name"
|
||||
msgstr "Nom de profil premium"
|
||||
@ -430,10 +418,6 @@ msgstr "Salaire"
|
||||
msgid "Save $60 or $120"
|
||||
msgstr "Économisez 60 $ ou 120 $"
|
||||
|
||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||
#~ msgid "Search languages..."
|
||||
#~ msgstr "Search languages..."
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||
msgstr "De manière sécurisée. Nos centres de données sont situés à Francfort (Allemagne), ce qui nous permet de bénéficier des meilleures lois locales sur la confidentialité. Nous sommes très conscients de la nature sensible de nos données et suivons les meilleures pratiques pour garantir la sécurité et l'intégrité des données qui nous sont confiées."
|
||||
@ -482,7 +466,7 @@ msgstr "Intelligent."
|
||||
msgid "Star on GitHub"
|
||||
msgstr "Étoile sur GitHub"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:226
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||
msgid "Stars"
|
||||
msgstr "Étoiles"
|
||||
|
||||
@ -517,7 +501,7 @@ msgstr "Boutique de modèles (Bientôt)."
|
||||
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||
msgstr "C'est génial. Vous pouvez consulter les <0>Problèmes</0> actuels et rejoindre notre <1>Communauté Discord</1> pour rester à jour sur ce qui est actuellement prioritaire. Dans tous les cas, nous sommes une communauté ouverte et accueillons toutes les contributions, techniques et non techniques ❤️"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:293
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:292
|
||||
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||
msgstr "Cette page évolue à mesure que nous apprenons ce qui fait une grande entreprise de signature. Nous la mettrons à jour lorsque nous aurons plus à partager."
|
||||
|
||||
@ -530,8 +514,8 @@ msgstr "Titre"
|
||||
msgid "Total Completed Documents"
|
||||
msgstr "Documents totalisés complétés"
|
||||
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:266
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:267
|
||||
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
|
||||
msgid "Total Customers"
|
||||
msgstr "Total des clients"
|
||||
|
||||
@ -618,4 +602,3 @@ msgstr "Vous pouvez auto-héberger Documenso gratuitement ou utiliser notre vers
|
||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||
msgid "Your browser does not support the video tag."
|
||||
msgstr "Votre navigateur ne prend pas en charge la balise vidéo."
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
import type { ReadonlyRequestCookies } from 'next/dist/server/web/spec-extension/adapters/request-cookies';
|
||||
|
||||
import type { I18n } from '@lingui/core';
|
||||
import type { I18n, MessageDescriptor } from '@lingui/core';
|
||||
|
||||
import { IS_APP_WEB, IS_APP_WEB_I18N_ENABLED } from '../constants/app';
|
||||
import type { I18nLocaleData, SupportedLanguageCodes } from '../constants/i18n';
|
||||
@ -10,7 +10,17 @@ export async function dynamicActivate(i18nInstance: I18n, locale: string) {
|
||||
const extension = process.env.NODE_ENV === 'development' ? 'po' : 'js';
|
||||
const context = IS_APP_WEB ? 'web' : 'marketing';
|
||||
|
||||
const { messages } = await import(`../translations/${locale}/${context}.${extension}`);
|
||||
let { messages } = await import(`../translations/${locale}/${context}.${extension}`);
|
||||
|
||||
// Dirty way to load common messages for development since it's not compiled.
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const commonMessages = await import(`../translations/${locale}/common.${extension}`);
|
||||
|
||||
messages = {
|
||||
...messages,
|
||||
...commonMessages.messages,
|
||||
};
|
||||
}
|
||||
|
||||
i18nInstance.loadAndActivate({ locale, messages });
|
||||
}
|
||||
@ -106,3 +116,7 @@ export const extractLocaleData = ({
|
||||
locales,
|
||||
};
|
||||
};
|
||||
|
||||
export const parseMessageDescriptor = (_: I18n['_'], value: string | MessageDescriptor) => {
|
||||
return typeof value === 'string' ? value : _(value);
|
||||
};
|
||||
|
||||
@ -4,7 +4,6 @@ export const isValidRedirectUrl = (value: string) => {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
|
||||
console.log({ protocol: url.protocol });
|
||||
if (!ALLOWED_PROTOCOLS.includes(url.protocol.slice(0, -1).toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
18
packages/lib/utils/remember.ts
Normal file
18
packages/lib/utils/remember.ts
Normal file
@ -0,0 +1,18 @@
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var, @typescript-eslint/no-explicit-any
|
||||
var __documenso_util_remember: Map<string, any>;
|
||||
}
|
||||
|
||||
export function remember<T>(name: string, getValue: () => T): T {
|
||||
const thusly = globalThis;
|
||||
|
||||
if (!thusly.__documenso_util_remember) {
|
||||
thusly.__documenso_util_remember = new Map();
|
||||
}
|
||||
|
||||
if (!thusly.__documenso_util_remember.has(name)) {
|
||||
thusly.__documenso_util_remember.set(name, getValue());
|
||||
}
|
||||
|
||||
return thusly.__documenso_util_remember.get(name);
|
||||
}
|
||||
36
packages/lib/utils/render-email-with-i18n.tsx
Normal file
36
packages/lib/utils/render-email-with-i18n.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
|
||||
import { render } from '@documenso/email/render';
|
||||
|
||||
import { getI18nInstance } from '../client-only/providers/i18n.server';
|
||||
import {
|
||||
APP_I18N_OPTIONS,
|
||||
type SupportedLanguageCodes,
|
||||
isValidLanguageCode,
|
||||
} from '../constants/i18n';
|
||||
|
||||
export const renderEmailWithI18N = async (
|
||||
component: React.ReactElement,
|
||||
options?: {
|
||||
plainText?: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
lang?: SupportedLanguageCodes | (string & {});
|
||||
},
|
||||
) => {
|
||||
try {
|
||||
const providedLang = options?.lang;
|
||||
|
||||
const lang = isValidLanguageCode(providedLang) ? providedLang : APP_I18N_OPTIONS.sourceLang;
|
||||
|
||||
const i18n = await getI18nInstance(lang);
|
||||
|
||||
i18n.activate(lang);
|
||||
|
||||
return render(<I18nProvider i18n={i18n}>{component}</I18nProvider>, {
|
||||
plainText: options?.plainText,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Failed to render email');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user