diff --git a/ee/pages/api/integrations/stripepayment/webhook.ts b/ee/pages/api/integrations/stripepayment/webhook.ts index adeb88c1..817c4922 100644 --- a/ee/pages/api/integrations/stripepayment/webhook.ts +++ b/ee/pages/api/integrations/stripepayment/webhook.ts @@ -10,7 +10,6 @@ import { getErrorFromUnknown } from "@lib/errors"; import EventManager from "@lib/events/EventManager"; import { CalendarEvent } from "@lib/integrations/calendar/interfaces/Calendar"; import prisma from "@lib/prisma"; -import { Ensure } from "@lib/types/utils"; import { getTranslation } from "@server/lib/i18n"; @@ -78,18 +77,35 @@ async function handlePaymentSuccess(event: Stripe.Event) { if (!user) throw new Error("No user found"); const t = await getTranslation(user.locale ?? "en", "common"); + const attendeesListPromises = booking.attendees.map(async (attendee) => { + return { + name: attendee.name, + email: attendee.email, + timeZone: attendee.timeZone, + language: { + translate: await getTranslation(attendee.locale ?? "en", "common"), + locale: attendee.locale ?? "en", + }, + }; + }); - const evt: Ensure = { + const attendeesList = await Promise.all(attendeesListPromises); + + const evt: CalendarEvent = { type: booking.title, title: booking.title, description: booking.description || undefined, startTime: booking.startTime.toISOString(), endTime: booking.endTime.toISOString(), - organizer: { email: user.email!, name: user.name!, timeZone: user.timeZone }, - attendees: booking.attendees, + organizer: { + email: user.email!, + name: user.name!, + timeZone: user.timeZone, + language: { translate: t, locale: user.locale ?? "en" }, + }, + attendees: attendeesList, uid: booking.uid, destinationCalendar: booking.destinationCalendar || user.destinationCalendar, - language: t, }; if (booking.location) evt.location = booking.location; diff --git a/lib/CalEventParser.ts b/lib/CalEventParser.ts index c525502d..b8bbb681 100644 --- a/lib/CalEventParser.ts +++ b/lib/CalEventParser.ts @@ -13,14 +13,14 @@ const translator = short(); export const getWhat = (calEvent: CalendarEvent) => { return ` -${calEvent.language("what")}: +${calEvent.organizer.language.translate("what")}: ${calEvent.type} `; }; export const getWhen = (calEvent: CalendarEvent) => { return ` -${calEvent.language("invitee_timezone")}: +${calEvent.organizer.language.translate("invitee_timezone")}: ${calEvent.attendees[0].timeZone} `; }; @@ -29,26 +29,26 @@ export const getWho = (calEvent: CalendarEvent) => { const attendees = calEvent.attendees .map((attendee) => { return ` -${attendee?.name || calEvent.language("guest")} +${attendee?.name || calEvent.organizer.language.translate("guest")} ${attendee.email} `; }) .join(""); const organizer = ` -${calEvent.organizer.name} - ${calEvent.language("organizer")} +${calEvent.organizer.name} - ${calEvent.organizer.language.translate("organizer")} ${calEvent.organizer.email} `; return ` -${calEvent.language("who")}: +${calEvent.organizer.language.translate("who")}: ${organizer + attendees} `; }; export const getAdditionalNotes = (calEvent: CalendarEvent) => { return ` -${calEvent.language("additional_notes")}: +${calEvent.organizer.language.translate("additional_notes")}: ${calEvent.description} `; }; @@ -74,7 +74,7 @@ export const getLocation = (calEvent: CalendarEvent) => { export const getManageLink = (calEvent: CalendarEvent) => { return ` -${calEvent.language("need_to_reschedule_or_cancel")} +${calEvent.organizer.language.translate("need_to_reschedule_or_cancel")} ${getCancelLink(calEvent)} `; }; @@ -96,7 +96,7 @@ export const getRichDescription = (calEvent: CalendarEvent, attendee?: Person) = ${getWhat(calEvent)} ${getWhen(calEvent)} ${getWho(calEvent)} -${calEvent.language("where")}: +${calEvent.organizer.language.translate("where")}: ${getLocation(calEvent)} ${getAdditionalNotes(calEvent)} `.trim(); @@ -106,7 +106,7 @@ ${getAdditionalNotes(calEvent)} ${getWhat(calEvent)} ${getWhen(calEvent)} ${getWho(calEvent)} -${calEvent.language("where")}: +${calEvent.organizer.language.translate("where")}: ${getLocation(calEvent)} ${getAdditionalNotes(calEvent)} ${getManageLink(calEvent)} diff --git a/lib/emails/templates/attendee-awaiting-payment-email.ts b/lib/emails/templates/attendee-awaiting-payment-email.ts index e9215e06..7455ecfd 100644 --- a/lib/emails/templates/attendee-awaiting-payment-email.ts +++ b/lib/emails/templates/attendee-awaiting-payment-email.ts @@ -25,14 +25,14 @@ export default class AttendeeAwaitingPaymentEmail extends AttendeeScheduledEmail to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, replyTo: this.calEvent.organizer.email, - subject: `${this.calEvent.language("awaiting_payment_subject", { + subject: `${this.calEvent.attendees[0].language.translate("awaiting_payment_subject", { eventType: this.calEvent.type, name: this.calEvent.team?.name || this.calEvent.organizer.name, date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("MMMM").toLowerCase() )} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`, })}`, @@ -43,8 +43,8 @@ export default class AttendeeAwaitingPaymentEmail extends AttendeeScheduledEmail protected getTextBody(): string { return ` -${this.calEvent.language("meeting_awaiting_payment")} -${this.calEvent.language("emailed_you_and_any_other_attendees")} +${this.calEvent.attendees[0].language.translate("meeting_awaiting_payment")} +${this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees")} ${this.getWhat()} ${this.getWhen()} ${this.getLocation()} @@ -53,14 +53,14 @@ ${this.getAdditionalNotes()} } protected getHtmlBody(): string { - const headerContent = this.calEvent.language("awaiting_payment_subject", { + const headerContent = this.calEvent.attendees[0].language.translate("awaiting_payment_subject", { eventType: this.calEvent.type, name: this.calEvent.team?.name || this.calEvent.organizer.name, date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("MMMM").toLowerCase() )} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`, }); @@ -73,8 +73,8 @@ ${this.getAdditionalNotes()}
${emailSchedulingBodyHeader("calendarCircle")} ${emailScheduledBodyHeaderContent( - this.calEvent.language("meeting_awaiting_payment"), - this.calEvent.language("emailed_you_and_any_other_attendees") + this.calEvent.attendees[0].language.translate("meeting_awaiting_payment"), + this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees") )} ${emailSchedulingBodyDivider()} @@ -148,7 +148,7 @@ ${this.getAdditionalNotes()} } protected getManageLink(): string { - const manageText = this.calEvent.language("pay_now"); + const manageText = this.calEvent.attendees[0].language.translate("pay_now"); if (this.calEvent.paymentInfo) { return ` diff --git a/lib/emails/templates/attendee-cancelled-email.ts b/lib/emails/templates/attendee-cancelled-email.ts index 55449e0a..9e60566f 100644 --- a/lib/emails/templates/attendee-cancelled-email.ts +++ b/lib/emails/templates/attendee-cancelled-email.ts @@ -24,14 +24,14 @@ export default class AttendeeCancelledEmail extends AttendeeScheduledEmail { to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, replyTo: this.calEvent.organizer.email, - subject: `${this.calEvent.language("event_cancelled_subject", { + subject: `${this.calEvent.attendees[0].language.translate("event_cancelled_subject", { eventType: this.calEvent.type, name: this.calEvent.team?.name || this.calEvent.organizer.name, date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("MMMM").toLowerCase() )} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`, })}`, @@ -42,8 +42,8 @@ export default class AttendeeCancelledEmail extends AttendeeScheduledEmail { protected getTextBody(): string { return ` -${this.calEvent.language("event_request_cancelled")} -${this.calEvent.language("emailed_you_and_any_other_attendees")} +${this.calEvent.attendees[0].language.translate("event_request_cancelled")} +${this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees")} ${this.getWhat()} ${this.getWhen()} ${this.getLocation()} @@ -52,14 +52,14 @@ ${this.getAdditionalNotes()} } protected getHtmlBody(): string { - const headerContent = this.calEvent.language("event_cancelled_subject", { + const headerContent = this.calEvent.attendees[0].language.translate("event_cancelled_subject", { eventType: this.calEvent.type, name: this.calEvent.team?.name || this.calEvent.organizer.name, date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("MMMM").toLowerCase() )} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`, }); @@ -73,8 +73,8 @@ ${this.getAdditionalNotes()}
${emailSchedulingBodyHeader("xCircle")} ${emailScheduledBodyHeaderContent( - this.calEvent.language("event_request_cancelled"), - this.calEvent.language("emailed_you_and_any_other_attendees") + this.calEvent.attendees[0].language.translate("event_request_cancelled"), + this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees") )} ${emailSchedulingBodyDivider()} diff --git a/lib/emails/templates/attendee-declined-email.ts b/lib/emails/templates/attendee-declined-email.ts index 78afb3df..32c920e6 100644 --- a/lib/emails/templates/attendee-declined-email.ts +++ b/lib/emails/templates/attendee-declined-email.ts @@ -24,14 +24,14 @@ export default class AttendeeDeclinedEmail extends AttendeeScheduledEmail { to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, replyTo: this.calEvent.organizer.email, - subject: `${this.calEvent.language("event_declined_subject", { + subject: `${this.calEvent.attendees[0].language.translate("event_declined_subject", { eventType: this.calEvent.type, name: this.calEvent.team?.name || this.calEvent.organizer.name, date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("MMMM").toLowerCase() )} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`, })}`, @@ -42,8 +42,8 @@ export default class AttendeeDeclinedEmail extends AttendeeScheduledEmail { protected getTextBody(): string { return ` -${this.calEvent.language("event_request_declined")} -${this.calEvent.language("emailed_you_and_any_other_attendees")} +${this.calEvent.attendees[0].language.translate("event_request_declined")} +${this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees")} ${this.getWhat()} ${this.getWhen()} ${this.getLocation()} @@ -52,14 +52,14 @@ ${this.getAdditionalNotes()} } protected getHtmlBody(): string { - const headerContent = this.calEvent.language("event_declined_subject", { + const headerContent = this.calEvent.attendees[0].language.translate("event_declined_subject", { eventType: this.calEvent.type, name: this.calEvent.team?.name || this.calEvent.organizer.name, date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("MMMM").toLowerCase() )} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`, }); @@ -73,8 +73,8 @@ ${this.getAdditionalNotes()}
${emailSchedulingBodyHeader("xCircle")} ${emailScheduledBodyHeaderContent( - this.calEvent.language("event_request_declined"), - this.calEvent.language("emailed_you_and_any_other_attendees") + this.calEvent.attendees[0].language.translate("event_request_declined"), + this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees") )} ${emailSchedulingBodyDivider()} diff --git a/lib/emails/templates/attendee-rescheduled-email.ts b/lib/emails/templates/attendee-rescheduled-email.ts index 4e39e1d3..bf5e7d54 100644 --- a/lib/emails/templates/attendee-rescheduled-email.ts +++ b/lib/emails/templates/attendee-rescheduled-email.ts @@ -30,14 +30,14 @@ export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail { to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, replyTo: this.calEvent.organizer.email, - subject: `${this.calEvent.language("rescheduled_event_type_subject", { + subject: `${this.calEvent.attendees[0].language.translate("rescheduled_event_type_subject", { eventType: this.calEvent.type, name: this.calEvent.team?.name || this.calEvent.organizer.name, date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("MMMM").toLowerCase() )} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`, })}`, @@ -51,20 +51,20 @@ export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail { // Guests cannot if (this.attendee === this.calEvent.attendees[0]) { return ` - ${this.calEvent.language("event_has_been_rescheduled")} - ${this.calEvent.language("emailed_you_and_any_other_attendees")} + ${this.calEvent.attendees[0].language.translate("event_has_been_rescheduled")} + ${this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees")} ${this.getWhat()} ${this.getWhen()} ${this.getLocation()} ${this.getAdditionalNotes()} - ${this.calEvent.language("need_to_reschedule_or_cancel")} + ${this.calEvent.attendees[0].language.translate("need_to_reschedule_or_cancel")} ${getCancelLink(this.calEvent)} `.replace(/(<([^>]+)>)/gi, ""); } return ` -${this.calEvent.language("event_has_been_rescheduled")} -${this.calEvent.language("emailed_you_and_any_other_attendees")} +${this.calEvent.attendees[0].language.translate("event_has_been_rescheduled")} +${this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees")} ${this.getWhat()} ${this.getWhen()} ${this.getLocation()} @@ -73,14 +73,14 @@ ${this.getAdditionalNotes()} } protected getHtmlBody(): string { - const headerContent = this.calEvent.language("rescheduled_event_type_subject", { + const headerContent = this.calEvent.attendees[0].language.translate("rescheduled_event_type_subject", { eventType: this.calEvent.type, name: this.calEvent.team?.name || this.calEvent.organizer.name, date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("MMMM").toLowerCase() )} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`, }); @@ -93,8 +93,8 @@ ${this.getAdditionalNotes()}
${emailSchedulingBodyHeader("calendarCircle")} ${emailScheduledBodyHeaderContent( - this.calEvent.language("event_has_been_rescheduled"), - this.calEvent.language("emailed_you_and_any_other_attendees") + this.calEvent.attendees[0].language.translate("event_has_been_rescheduled"), + this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees") )} ${emailSchedulingBodyDivider()} diff --git a/lib/emails/templates/attendee-scheduled-email.ts b/lib/emails/templates/attendee-scheduled-email.ts index a3f16d42..832dfc1b 100644 --- a/lib/emails/templates/attendee-scheduled-email.ts +++ b/lib/emails/templates/attendee-scheduled-email.ts @@ -61,7 +61,7 @@ export default class AttendeeScheduledEmail { .map((v, i) => (i === 1 ? v + 1 : v)) as DateArray, startInputType: "utc", productId: "calendso/ics", - title: this.calEvent.language("ics_event_title", { + title: this.calEvent.attendees[0].language.translate("ics_event_title", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, }), @@ -89,14 +89,14 @@ export default class AttendeeScheduledEmail { to: `${this.attendee.name} <${this.attendee.email}>`, from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`, replyTo: this.calEvent.organizer.email, - subject: `${this.calEvent.language("confirmed_event_type_subject", { + subject: `${this.calEvent.attendees[0].language.translate("confirmed_event_type_subject", { eventType: this.calEvent.type, name: this.calEvent.team?.name || this.calEvent.organizer.name, date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("MMMM").toLowerCase() )} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`, })}`, @@ -114,8 +114,8 @@ export default class AttendeeScheduledEmail { protected getTextBody(): string { return ` -${this.calEvent.language("your_event_has_been_scheduled")} -${this.calEvent.language("emailed_you_and_any_other_attendees")} +${this.calEvent.attendees[0].language.translate("your_event_has_been_scheduled")} +${this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees")} ${getRichDescription(this.calEvent)} `.trim(); @@ -126,14 +126,14 @@ ${getRichDescription(this.calEvent)} } protected getHtmlBody(): string { - const headerContent = this.calEvent.language("confirmed_event_type_subject", { + const headerContent = this.calEvent.attendees[0].language.translate("confirmed_event_type_subject", { eventType: this.calEvent.type, name: this.calEvent.team?.name || this.calEvent.organizer.name, date: `${this.getInviteeStart().format("h:mma")} - ${this.getInviteeEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("MMMM").toLowerCase() )} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format("YYYY")}`, }); @@ -146,8 +146,8 @@ ${getRichDescription(this.calEvent)}
${emailSchedulingBodyHeader("checkCircle")} ${emailScheduledBodyHeaderContent( - this.calEvent.language("your_event_has_been_scheduled"), - this.calEvent.language("emailed_you_and_any_other_attendees") + this.calEvent.attendees[0].language.translate("your_event_has_been_scheduled"), + this.calEvent.attendees[0].language.translate("emailed_you_and_any_other_attendees") )} ${emailSchedulingBodyDivider()} @@ -219,8 +219,8 @@ ${getRichDescription(this.calEvent)} // Only the original attendee can make changes to the event // Guests cannot if (this.attendee === this.calEvent.attendees[0]) { - const manageText = this.calEvent.language("manage_this_event"); - return `

${this.calEvent.language( + const manageText = this.calEvent.attendees[0].language.translate("manage_this_event"); + return `

${this.calEvent.attendees[0].language.translate( "need_to_reschedule_or_cancel" )}

-

${this.calEvent.language("what")}

+

${this.calEvent.attendees[0].language.translate("what")}

${this.calEvent.type}

`; } @@ -242,11 +242,11 @@ ${getRichDescription(this.calEvent)} return `

-

${this.calEvent.language("when")}

+

${this.calEvent.attendees[0].language.translate("when")}

- ${this.calEvent.language( + ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.attendees[0].language.translate( this.getInviteeStart().format("MMMM").toLowerCase() )} ${this.getInviteeStart().format("D")}, ${this.getInviteeStart().format( "YYYY" @@ -261,7 +261,7 @@ ${getRichDescription(this.calEvent)} const attendees = this.calEvent.attendees .map((attendee) => { return `

`; @@ -270,14 +270,16 @@ ${getRichDescription(this.calEvent)} const organizer = `
${ this.calEvent.organizer.name - } - ${this.calEvent.language("organizer")} ${this.calEvent.organizer.email}
`; return `

-

${this.calEvent.language("who")}

+

${this.calEvent.attendees[0].language.translate("who")}

${organizer + attendees}
`; } @@ -286,7 +288,7 @@ ${getRichDescription(this.calEvent)} return `

-

${this.calEvent.language("additional_notes")}

+

${this.calEvent.attendees[0].language.translate("additional_notes")}

${this.calEvent.description}

`; @@ -308,30 +310,30 @@ ${getRichDescription(this.calEvent)} return `

-

${this.calEvent.language("where")}

+

${this.calEvent.attendees[0].language.translate("where")}

${providerName} ${ meetingUrl && - `` }

${ meetingId && - `
${this.calEvent.language( + `
${this.calEvent.attendees[0].language.translate( "meeting_id" )}: ${meetingId}
` } ${ meetingPassword && - `
${this.calEvent.language( + `
${this.calEvent.attendees[0].language.translate( "meeting_password" )}: ${meetingPassword}
` } ${ meetingUrl && - `
${this.calEvent.language( + `
${this.calEvent.attendees[0].language.translate( "meeting_url" - )}: ${meetingUrl}
` } @@ -345,14 +347,14 @@ ${getRichDescription(this.calEvent)} return `

-

${this.calEvent.language("where")}

+

${this.calEvent.attendees[0].language.translate("where")}

${providerName} ${ hangoutLink && - `` }

-
@@ -362,7 +364,7 @@ ${getRichDescription(this.calEvent)} return `

-

${this.calEvent.language("where")}

+

${this.calEvent.attendees[0].language.translate("where")}

${ providerName || this.calEvent.location }

diff --git a/lib/emails/templates/organizer-cancelled-email.ts b/lib/emails/templates/organizer-cancelled-email.ts index 4845b801..d6517118 100644 --- a/lib/emails/templates/organizer-cancelled-email.ts +++ b/lib/emails/templates/organizer-cancelled-email.ts @@ -33,14 +33,14 @@ export default class OrganizerCancelledEmail extends OrganizerScheduledEmail { return { from: `Cal.com <${this.getMailerOptions().from}>`, to: toAddresses.join(","), - subject: `${this.calEvent.language("event_cancelled_subject", { + subject: `${this.calEvent.organizer.language.translate("event_cancelled_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, })}`, @@ -51,8 +51,8 @@ export default class OrganizerCancelledEmail extends OrganizerScheduledEmail { protected getTextBody(): string { return ` -${this.calEvent.language("event_request_cancelled")} -${this.calEvent.language("emailed_you_and_any_other_attendees")} +${this.calEvent.organizer.language.translate("event_request_cancelled")} +${this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")} ${this.getWhat()} ${this.getWhen()} ${this.getLocation()} @@ -61,14 +61,14 @@ ${this.getAdditionalNotes()} } protected getHtmlBody(): string { - const headerContent = this.calEvent.language("event_cancelled_subject", { + const headerContent = this.calEvent.organizer.language.translate("event_cancelled_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, }); @@ -81,8 +81,8 @@ ${this.getAdditionalNotes()}
${emailSchedulingBodyHeader("xCircle")} ${emailScheduledBodyHeaderContent( - this.calEvent.language("event_request_cancelled"), - this.calEvent.language("emailed_you_and_any_other_attendees") + this.calEvent.organizer.language.translate("event_request_cancelled"), + this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees") )} ${emailSchedulingBodyDivider()} diff --git a/lib/emails/templates/organizer-payment-refund-failed-email.ts b/lib/emails/templates/organizer-payment-refund-failed-email.ts index 2b97296f..6b254980 100644 --- a/lib/emails/templates/organizer-payment-refund-failed-email.ts +++ b/lib/emails/templates/organizer-payment-refund-failed-email.ts @@ -27,14 +27,14 @@ export default class OrganizerPaymentRefundFailedEmail extends OrganizerSchedule return { from: `Cal.com <${this.getMailerOptions().from}>`, to: toAddresses.join(","), - subject: `${this.calEvent.language("refund_failed_subject", { + subject: `${this.calEvent.organizer.language.translate("refund_failed_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, })}`, @@ -45,11 +45,15 @@ export default class OrganizerPaymentRefundFailedEmail extends OrganizerSchedule protected getTextBody(): string { return ` -${this.calEvent.language("a_refund_failed")} -${this.calEvent.language("check_with_provider_and_user", { user: this.calEvent.attendees[0].name })} +${this.calEvent.organizer.language.translate("a_refund_failed")} +${this.calEvent.organizer.language.translate("check_with_provider_and_user", { + user: this.calEvent.attendees[0].name, +})} ${ this.calEvent.paymentInfo && - this.calEvent.language("error_message", { errorMessage: this.calEvent.paymentInfo.reason }) + this.calEvent.organizer.language.translate("error_message", { + errorMessage: this.calEvent.paymentInfo.reason, + }) } ${this.getWhat()} ${this.getWhen()} @@ -59,14 +63,14 @@ ${this.getAdditionalNotes()} } protected getHtmlBody(): string { - const headerContent = this.calEvent.language("refund_failed_subject", { + const headerContent = this.calEvent.organizer.language.translate("refund_failed_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, }); @@ -91,14 +95,14 @@ ${this.getAdditionalNotes()} -
${this.calEvent.language( +
${this.calEvent.organizer.language.translate( "a_refund_failed" )}
-
${this.calEvent.language( +
${this.calEvent.organizer.language.translate( "check_with_provider_and_user", { user: this.calEvent.attendees[0].name } )}
@@ -174,7 +178,7 @@ ${this.getAdditionalNotes()} refundInformation = ` -
${this.calEvent.language( +
${this.calEvent.organizer.language.translate( "error_message", { errorMessage: paymentInfo.reason } )}
diff --git a/lib/emails/templates/organizer-request-email.ts b/lib/emails/templates/organizer-request-email.ts index e0e30bec..36c2817a 100644 --- a/lib/emails/templates/organizer-request-email.ts +++ b/lib/emails/templates/organizer-request-email.ts @@ -34,14 +34,14 @@ export default class OrganizerRequestEmail extends OrganizerScheduledEmail { return { from: `Cal.com <${this.getMailerOptions().from}>`, to: toAddresses.join(","), - subject: `${this.calEvent.language("event_awaiting_approval_subject", { + subject: `${this.calEvent.organizer.language.translate("event_awaiting_approval_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, })}`, @@ -52,26 +52,26 @@ export default class OrganizerRequestEmail extends OrganizerScheduledEmail { protected getTextBody(): string { return ` -${this.calEvent.language("event_awaiting_approval")} -${this.calEvent.language("someone_requested_an_event")} +${this.calEvent.organizer.language.translate("event_awaiting_approval")} +${this.calEvent.organizer.language.translate("someone_requested_an_event")} ${this.getWhat()} ${this.getWhen()} ${this.getLocation()} ${this.getAdditionalNotes()} -${this.calEvent.language("confirm_or_reject_request")} +${this.calEvent.organizer.language.translate("confirm_or_reject_request")} ${process.env.BASE_URL} + "/bookings/upcoming" `.replace(/(<([^>]+)>)/gi, ""); } protected getHtmlBody(): string { - const headerContent = this.calEvent.language("event_awaiting_approval_subject", { + const headerContent = this.calEvent.organizer.language.translate("event_awaiting_approval_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, }); @@ -85,8 +85,8 @@ ${process.env.BASE_URL} + "/bookings/upcoming"
${emailSchedulingBodyHeader("calendarCircle")} ${emailScheduledBodyHeaderContent( - this.calEvent.language("event_awaiting_approval"), - this.calEvent.language("someone_requested_an_event") + this.calEvent.organizer.language.translate("event_awaiting_approval"), + this.calEvent.organizer.language.translate("someone_requested_an_event") )} ${emailSchedulingBodyDivider()} @@ -166,7 +166,7 @@ ${process.env.BASE_URL} + "/bookings/upcoming" } protected getManageLink(): string { - const manageText = this.calEvent.language("confirm_or_reject_request"); + const manageText = this.calEvent.organizer.language.translate("confirm_or_reject_request"); const manageLink = process.env.BASE_URL + "/bookings/upcoming"; return `${manageText} `; } diff --git a/lib/emails/templates/organizer-request-reminder-email.ts b/lib/emails/templates/organizer-request-reminder-email.ts index 2094cf5c..3d7c8baf 100644 --- a/lib/emails/templates/organizer-request-reminder-email.ts +++ b/lib/emails/templates/organizer-request-reminder-email.ts @@ -34,14 +34,14 @@ export default class OrganizerRequestReminderEmail extends OrganizerScheduledEma return { from: `Cal.com <${this.getMailerOptions().from}>`, to: toAddresses.join(","), - subject: `${this.calEvent.language("event_awaiting_approval_subject", { + subject: `${this.calEvent.organizer.language.translate("event_awaiting_approval_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, })}`, @@ -52,26 +52,26 @@ export default class OrganizerRequestReminderEmail extends OrganizerScheduledEma protected getTextBody(): string { return ` -${this.calEvent.language("event_still_awaiting_approval")} -${this.calEvent.language("someone_requested_an_event")} +${this.calEvent.organizer.language.translate("event_still_awaiting_approval")} +${this.calEvent.organizer.language.translate("someone_requested_an_event")} ${this.getWhat()} ${this.getWhen()} ${this.getLocation()} ${this.getAdditionalNotes()} -${this.calEvent.language("confirm_or_reject_request")} +${this.calEvent.organizer.language.translate("confirm_or_reject_request")} ${process.env.BASE_URL} + "/bookings/upcoming" `.replace(/(<([^>]+)>)/gi, ""); } protected getHtmlBody(): string { - const headerContent = this.calEvent.language("event_awaiting_approval_subject", { + const headerContent = this.calEvent.organizer.language.translate("event_awaiting_approval_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, }); @@ -84,8 +84,8 @@ ${process.env.BASE_URL} + "/bookings/upcoming"
${emailSchedulingBodyHeader("calendarCircle")} ${emailScheduledBodyHeaderContent( - this.calEvent.language("event_still_awaiting_approval"), - this.calEvent.language("someone_requested_an_event") + this.calEvent.organizer.language.translate("event_still_awaiting_approval"), + this.calEvent.organizer.language.translate("someone_requested_an_event") )} ${emailSchedulingBodyDivider()} @@ -165,7 +165,7 @@ ${process.env.BASE_URL} + "/bookings/upcoming" } protected getManageLink(): string { - const manageText = this.calEvent.language("confirm_or_reject_request"); + const manageText = this.calEvent.organizer.language.translate("confirm_or_reject_request"); const manageLink = process.env.BASE_URL + "/bookings/upcoming"; return `${manageText} `; } diff --git a/lib/emails/templates/organizer-rescheduled-email.ts b/lib/emails/templates/organizer-rescheduled-email.ts index d732cca6..da267249 100644 --- a/lib/emails/templates/organizer-rescheduled-email.ts +++ b/lib/emails/templates/organizer-rescheduled-email.ts @@ -39,14 +39,14 @@ export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail { }, from: `Cal.com <${this.getMailerOptions().from}>`, to: toAddresses.join(","), - subject: `${this.calEvent.language("rescheduled_event_type_subject", { + subject: `${this.calEvent.organizer.language.translate("rescheduled_event_type_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, })}`, @@ -57,26 +57,26 @@ export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail { protected getTextBody(): string { return ` -${this.calEvent.language("event_has_been_rescheduled")} -${this.calEvent.language("emailed_you_and_any_other_attendees")} +${this.calEvent.organizer.language.translate("event_has_been_rescheduled")} +${this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")} ${this.getWhat()} ${this.getWhen()} ${this.getLocation()} ${this.getAdditionalNotes()} -${this.calEvent.language("need_to_reschedule_or_cancel")} +${this.calEvent.organizer.language.translate("need_to_reschedule_or_cancel")} ${getCancelLink(this.calEvent)} `.replace(/(<([^>]+)>)/gi, ""); } protected getHtmlBody(): string { - const headerContent = this.calEvent.language("rescheduled_event_type_subject", { + const headerContent = this.calEvent.organizer.language.translate("rescheduled_event_type_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, }); @@ -89,8 +89,8 @@ ${getCancelLink(this.calEvent)}
${emailSchedulingBodyHeader("calendarCircle")} ${emailScheduledBodyHeaderContent( - this.calEvent.language("event_has_been_rescheduled"), - this.calEvent.language("emailed_you_and_any_other_attendees") + this.calEvent.organizer.language.translate("event_has_been_rescheduled"), + this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees") )} ${emailSchedulingBodyDivider()} diff --git a/lib/emails/templates/organizer-scheduled-email.ts b/lib/emails/templates/organizer-scheduled-email.ts index 81b412c5..693f58bc 100644 --- a/lib/emails/templates/organizer-scheduled-email.ts +++ b/lib/emails/templates/organizer-scheduled-email.ts @@ -59,7 +59,7 @@ export default class OrganizerScheduledEmail { .map((v, i) => (i === 1 ? v + 1 : v)) as DateArray, startInputType: "utc", productId: "calendso/ics", - title: this.calEvent.language("ics_event_title", { + title: this.calEvent.organizer.language.translate("ics_event_title", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, }), @@ -96,14 +96,14 @@ export default class OrganizerScheduledEmail { }, from: `Cal.com <${this.getMailerOptions().from}>`, to: toAddresses.join(","), - subject: `${this.calEvent.language("confirmed_event_type_subject", { + subject: `${this.calEvent.organizer.language.translate("confirmed_event_type_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, })}`, @@ -121,8 +121,8 @@ export default class OrganizerScheduledEmail { protected getTextBody(): string { return ` -${this.calEvent.language("new_event_scheduled")} -${this.calEvent.language("emailed_you_and_any_other_attendees")} +${this.calEvent.organizer.language.translate("new_event_scheduled")} +${this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees")} ${getRichDescription(this.calEvent)} `.trim(); @@ -133,14 +133,14 @@ ${getRichDescription(this.calEvent)} } protected getHtmlBody(): string { - const headerContent = this.calEvent.language("confirmed_event_type_subject", { + const headerContent = this.calEvent.organizer.language.translate("confirmed_event_type_subject", { eventType: this.calEvent.type, name: this.calEvent.attendees[0].name, date: `${this.getOrganizerStart().format("h:mma")} - ${this.getOrganizerEnd().format( "h:mma" - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format("YYYY")}`, }); @@ -153,8 +153,8 @@ ${getRichDescription(this.calEvent)}
${emailSchedulingBodyHeader("checkCircle")} ${emailScheduledBodyHeaderContent( - this.calEvent.language("new_event_scheduled"), - this.calEvent.language("emailed_you_and_any_other_attendees") + this.calEvent.organizer.language.translate("new_event_scheduled"), + this.calEvent.organizer.language.translate("emailed_you_and_any_other_attendees") )} ${emailSchedulingBodyDivider()} @@ -223,8 +223,8 @@ ${getRichDescription(this.calEvent)} } protected getManageLink(): string { - const manageText = this.calEvent.language("manage_this_event"); - return `

${this.calEvent.language( + const manageText = this.calEvent.organizer.language.translate("manage_this_event"); + return `

${this.calEvent.organizer.language.translate( "need_to_reschedule_or_cancel" )}

-

${this.calEvent.language("what")}

+

${this.calEvent.organizer.language.translate("what")}

${this.calEvent.type}

`; } @@ -243,11 +243,11 @@ ${getRichDescription(this.calEvent)} return `

-

${this.calEvent.language("when")}

+

${this.calEvent.organizer.language.translate("when")}

- ${this.calEvent.language( + ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("dddd").toLowerCase() - )}, ${this.calEvent.language( + )}, ${this.calEvent.organizer.language.translate( this.getOrganizerStart().format("MMMM").toLowerCase() )} ${this.getOrganizerStart().format("D")}, ${this.getOrganizerStart().format( "YYYY" @@ -262,7 +262,7 @@ ${getRichDescription(this.calEvent)} const attendees = this.calEvent.attendees .map((attendee) => { return `

`; @@ -271,14 +271,16 @@ ${getRichDescription(this.calEvent)} const organizer = `
${ this.calEvent.organizer.name - } - ${this.calEvent.language("organizer")} ${this.calEvent.organizer.email}
`; return `

-

${this.calEvent.language("who")}

+

${this.calEvent.organizer.language.translate("who")}

${organizer + attendees}
`; } @@ -287,7 +289,7 @@ ${getRichDescription(this.calEvent)} return `

-

${this.calEvent.language("additional_notes")}

+

${this.calEvent.organizer.language.translate("additional_notes")}

${this.calEvent.description}

`; @@ -309,30 +311,30 @@ ${getRichDescription(this.calEvent)} return `

-

${this.calEvent.language("where")}

+

${this.calEvent.organizer.language.translate("where")}

${providerName} ${ meetingUrl && - `` }

${ meetingId && - `
${this.calEvent.language( + `
${this.calEvent.organizer.language.translate( "meeting_id" )}: ${meetingId}
` } ${ meetingPassword && - `
${this.calEvent.language( + `
${this.calEvent.organizer.language.translate( "meeting_password" )}: ${meetingPassword}
` } ${ meetingUrl && - `
${this.calEvent.language( + `
${this.calEvent.organizer.language.translate( "meeting_url" - )}: ${meetingUrl}
` } @@ -346,14 +348,14 @@ ${getRichDescription(this.calEvent)} return `

-

${this.calEvent.language("where")}

+

${this.calEvent.organizer.language.translate("where")}

${providerName} ${ hangoutLink && - `` }

-
@@ -363,7 +365,7 @@ ${getRichDescription(this.calEvent)} return `

-

${this.calEvent.language("where")}

+

${this.calEvent.organizer.language.translate("where")}

${ providerName || this.calEvent.location }

diff --git a/lib/events/EventManager.ts b/lib/events/EventManager.ts index 2cfdaacc..f1561b0d 100644 --- a/lib/events/EventManager.ts +++ b/lib/events/EventManager.ts @@ -8,7 +8,6 @@ import { createEvent, updateEvent } from "@lib/integrations/calendar/CalendarMan import { AdditionInformation, CalendarEvent } from "@lib/integrations/calendar/interfaces/Calendar"; import { LocationType } from "@lib/location"; import prisma from "@lib/prisma"; -import { Ensure } from "@lib/types/utils"; import { createMeeting, updateMeeting, VideoCallData } from "@lib/videoClient"; export type Event = AdditionInformation & VideoCallData; @@ -118,7 +117,7 @@ export default class EventManager { * * @param event */ - public async create(event: Ensure): Promise { + public async create(event: CalendarEvent): Promise { const evt = processLocation(event); const isDedicated = evt.location ? isDedicatedIntegration(evt.location) : null; @@ -158,10 +157,7 @@ export default class EventManager { * * @param event */ - public async update( - event: Ensure, - rescheduleUid: string - ): Promise { + public async update(event: CalendarEvent, rescheduleUid: string): Promise { const evt = processLocation(event); if (!rescheduleUid) { @@ -293,7 +289,7 @@ export default class EventManager { * @param event * @private */ - private createVideoEvent(event: Ensure): Promise { + private createVideoEvent(event: CalendarEvent): Promise { const credential = this.getVideoCredential(event); if (credential) { diff --git a/lib/integrations/calendar/interfaces/Calendar.ts b/lib/integrations/calendar/interfaces/Calendar.ts index a77504ea..53960c1b 100644 --- a/lib/integrations/calendar/interfaces/Calendar.ts +++ b/lib/integrations/calendar/interfaces/Calendar.ts @@ -13,6 +13,7 @@ export type Person = { name: string; email: string; timeZone: string; + language: { translate: TFunction; locale: string }; }; export interface EntryPoint { @@ -46,7 +47,6 @@ export interface CalendarEvent { organizer: Person; attendees: Person[]; conferenceData?: ConferenceData; - language: TFunction; additionInformation?: AdditionInformation; uid?: string | null; videoCallData?: VideoCallData; diff --git a/lib/videoClient.ts b/lib/videoClient.ts index cd1cdd3e..e8869188 100644 --- a/lib/videoClient.ts +++ b/lib/videoClient.ts @@ -10,7 +10,6 @@ import logger from "@lib/logger"; import DailyVideoApiAdapter from "./integrations/Daily/DailyVideoApiAdapter"; import ZoomVideoApiAdapter from "./integrations/Zoom/ZoomVideoApiAdapter"; import { CalendarEvent } from "./integrations/calendar/interfaces/Calendar"; -import { Ensure } from "./types/utils"; const log = logger.getChildLogger({ prefix: ["[lib] videoClient"] }); @@ -56,10 +55,7 @@ const getBusyVideoTimes = (withCredentials: Credential[]) => results.reduce((acc, availability) => acc.concat(availability), []) ); -const createMeeting = async ( - credential: Credential, - calEvent: Ensure -): Promise => { +const createMeeting = async (credential: Credential, calEvent: CalendarEvent): Promise => { const uid: string = getUid(calEvent); if (!credential) { diff --git a/lib/webhooks/sendPayload.tsx b/lib/webhooks/sendPayload.tsx index 8ed67c1f..4eb97437 100644 --- a/lib/webhooks/sendPayload.tsx +++ b/lib/webhooks/sendPayload.tsx @@ -4,7 +4,7 @@ import { CalendarEvent } from "@lib/integrations/calendar/interfaces/Calendar"; type ContentType = "application/json" | "application/x-www-form-urlencoded"; -function applyTemplate(template: string, data: Omit, contentType: ContentType) { +function applyTemplate(template: string, data: CalendarEvent, contentType: ContentType) { const compiled = compile(template)(data); if (contentType === "application/json") { return jsonParse(compiled); @@ -25,7 +25,9 @@ const sendPayload = async ( triggerEvent: string, createdAt: string, subscriberUrl: string, - data: Omit & { metadata?: { [key: string]: string } }, + data: CalendarEvent & { + metadata?: { [key: string]: string }; + }, template?: string | null ) => { if (!subscriberUrl || !data) { diff --git a/pages/api/book/confirm.ts b/pages/api/book/confirm.ts index e92157ba..a2d43fe3 100644 --- a/pages/api/book/confirm.ts +++ b/pages/api/book/confirm.ts @@ -43,8 +43,6 @@ const authorized = async ( const log = logger.getChildLogger({ prefix: ["[api] book:user"] }); export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise { - const t = await getTranslation(req.body.language ?? "en", "common"); - const session = await getSession({ req: req }); if (!session?.user?.id) { return res.status(401).json({ message: "Not authenticated" }); @@ -71,6 +69,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) name: true, username: true, destinationCalendar: true, + locale: true, }, }); @@ -78,6 +77,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return res.status(404).json({ message: "User not found" }); } + const tOrganizer = await getTranslation(currentUser.locale ?? "en", "common"); + if (req.method === "PATCH") { const booking = await prisma.booking.findFirst({ where: { @@ -112,6 +113,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return res.status(400).json({ message: "booking already confirmed" }); } + const attendeesListPromises = booking.attendees.map(async (attendee) => { + return { + name: attendee.name, + email: attendee.email, + timeZone: attendee.timeZone, + language: { + translate: await getTranslation(attendee.locale ?? "en", "common"), + locale: attendee.locale ?? "en", + }, + }; + }); + + const attendeesList = await Promise.all(attendeesListPromises); + const evt: CalendarEvent = { type: booking.title, title: booking.title, @@ -122,11 +137,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) email: currentUser.email, name: currentUser.name || "Unnamed", timeZone: currentUser.timeZone, + language: { translate: tOrganizer, locale: currentUser.locale ?? "en" }, }, - attendees: booking.attendees, + attendees: attendeesList, location: booking.location ?? "", uid: booking.uid, - language: t, destinationCalendar: booking?.destinationCalendar || currentUser.destinationCalendar, }; diff --git a/pages/api/book/event.ts b/pages/api/book/event.ts index c426f471..d5766ef7 100644 --- a/pages/api/book/event.ts +++ b/pages/api/book/event.ts @@ -136,6 +136,7 @@ const userSelect = Prisma.validator()({ credentials: true, bufferTime: true, destinationCalendar: true, + locale: true, }, }); @@ -152,6 +153,7 @@ const getUserNameWithBookingCounts = async (eventTypeId: number, selectedUserNam select: { id: true, username: true, + locale: true, }, }); @@ -180,8 +182,8 @@ type User = Prisma.UserGetPayload; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const reqBody = req.body as BookingCreateBody; const eventTypeId = reqBody.eventTypeId; - const t = await getTranslation(reqBody.language ?? "en", "common"); - + const tAttendees = await getTranslation(reqBody.language ?? "en", "common"); + const tGuests = await getTranslation("en", "common"); log.debug(`Booking eventType ${eventTypeId} started`); const isTimeInPast = (time: string): boolean => { @@ -244,6 +246,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) users.push(eventTypeUser); } + const organizer = await prisma.user.findUnique({ + where: { + id: users[0].id, + }, + select: { + locale: true, + }, + }); + + const tOrganizer = await getTranslation(organizer?.locale ?? "en", "common"); + if (eventType.schedulingType === SchedulingType.ROUND_ROBIN) { const bookingCounts = await getUserNameWithBookingCounts( eventTypeId, @@ -253,25 +266,41 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) users = getLuckyUsers(users, bookingCounts); } - const invitee = [{ email: reqBody.email, name: reqBody.name, timeZone: reqBody.timeZone }]; + const invitee = [ + { + email: reqBody.email, + name: reqBody.name, + timeZone: reqBody.timeZone, + language: { translate: tAttendees, locale: reqBody.language ?? "en" }, + }, + ]; const guests = (reqBody.guests || []).map((guest) => { const g = { email: guest, name: "", timeZone: reqBody.timeZone, + language: { translate: tGuests, locale: "en" }, }; return g; }); - const teamMembers = + const teamMemberPromises = eventType.schedulingType === SchedulingType.COLLECTIVE - ? users.slice(1).map((user) => ({ - email: user.email || "", - name: user.name || "", - timeZone: user.timeZone, - })) + ? users.slice(1).map(async function (user) { + return { + email: user.email || "", + name: user.name || "", + timeZone: user.timeZone, + language: { + translate: await getTranslation(user.locale ?? "en", "common"), + locale: user.locale ?? "en", + }, + }; + }) : []; + const teamMembers = await Promise.all(teamMemberPromises); + const attendeesList = [...invitee, ...guests, ...teamMembers]; const seed = `${users[0].username}:${dayjs(req.body.start).utc().format()}:${new Date().getTime()}`; @@ -282,7 +311,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) eventType: eventType.title, eventName: eventType.eventName, host: users[0].name || "Nameless", - t, + t: tOrganizer, }; const description = @@ -294,7 +323,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const evt: CalendarEvent = { type: eventType.title, - title: getEventName(eventNameObject), + title: getEventName(eventNameObject), //this needs to be either forced in english, or fetched for each attendee and organizer separately description, startTime: reqBody.start, endTime: reqBody.end, @@ -302,10 +331,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) name: users[0].name || "Nameless", email: users[0].email || "Email-less", timeZone: users[0].timeZone, + language: { translate: tOrganizer, locale: organizer?.locale ?? "en" }, }, attendees: attendeesList, location: reqBody.location, // Will be processed by the EventManager later. - language: t, /** For team events, we will need to handle each member destinationCalendar eventually */ destinationCalendar: eventType.destinationCalendar || users[0].destinationCalendar, }; @@ -343,7 +372,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) }, attendees: { createMany: { - data: evt.attendees, + data: evt.attendees.map((attendee) => { + //if attendee is team member, it should fetch their locale not booker's locale + //perhaps make email fetch request to see if his locale is stored, else + const retObj = { + name: attendee.name, + email: attendee.email, + timeZone: attendee.timeZone, + locale: attendee.language.locale, + }; + return retObj; + }), }, }, user: { diff --git a/pages/api/cancel.ts b/pages/api/cancel.ts index bbaa775e..89106951 100644 --- a/pages/api/cancel.ts +++ b/pages/api/cancel.ts @@ -89,11 +89,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) name: true, email: true, timeZone: true, + locale: true, }, rejectOnNotFound: true, }); - const t = await getTranslation(req.body.language ?? "en", "common"); + const attendeesListPromises = bookingToDelete.attendees.map(async (attendee) => { + return { + name: attendee.name, + email: attendee.email, + timeZone: attendee.timeZone, + language: { + translate: await getTranslation(attendee.locale ?? "en", "common"), + locale: attendee.locale ?? "en", + }, + }; + }); + + const attendeesList = await Promise.all(attendeesListPromises); + const tOrganizer = await getTranslation(organizer.locale ?? "en", "common"); const evt: CalendarEvent = { title: bookingToDelete?.title, @@ -105,14 +119,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) email: organizer.email, name: organizer.name ?? "Nameless", timeZone: organizer.timeZone, + language: { translate: tOrganizer, locale: organizer.locale ?? "en" }, }, - attendees: bookingToDelete?.attendees.map((attendee) => { - const retObj = { name: attendee.name, email: attendee.email, timeZone: attendee.timeZone }; - return retObj; - }), + attendees: attendeesList, uid: bookingToDelete?.uid, location: bookingToDelete?.location, - language: t, destinationCalendar: bookingToDelete?.destinationCalendar || bookingToDelete?.user.destinationCalendar, }; @@ -168,11 +179,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) email: bookingToDelete.user?.email ?? "dev@calendso.com", name: bookingToDelete.user?.name ?? "no user", timeZone: bookingToDelete.user?.timeZone ?? "", + language: { translate: tOrganizer, locale: organizer.locale ?? "en" }, }, - attendees: bookingToDelete.attendees, + attendees: attendeesList, location: bookingToDelete.location ?? "", uid: bookingToDelete.uid ?? "", - language: t, destinationCalendar: bookingToDelete?.destinationCalendar || bookingToDelete?.user.destinationCalendar, }; await refund(bookingToDelete, evt); diff --git a/pages/api/cron/bookingReminder.ts b/pages/api/cron/bookingReminder.ts index c899317a..d4341d62 100644 --- a/pages/api/cron/bookingReminder.ts +++ b/pages/api/cron/bookingReminder.ts @@ -73,7 +73,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) continue; } - const t = await getTranslation(user.locale ?? "en", "common"); + const tOrganizer = await getTranslation(user.locale ?? "en", "common"); + + const attendeesListPromises = booking.attendees.map(async (attendee) => { + return { + name: attendee.name, + email: attendee.email, + timeZone: attendee.timeZone, + language: { + translate: await getTranslation(attendee.locale ?? "en", "common"), + locale: attendee.locale ?? "en", + }, + }; + }); + + const attendeesList = await Promise.all(attendeesListPromises); const evt: CalendarEvent = { type: booking.title, @@ -86,10 +100,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) email: user.email, name, timeZone: user.timeZone, + language: { translate: tOrganizer, locale: user.locale ?? "en" }, }, - attendees: booking.attendees, + attendees: attendeesList, uid: booking.uid, - language: t, destinationCalendar: booking.destinationCalendar || user.destinationCalendar, }; diff --git a/playwright/integrations.test.ts b/playwright/integrations.test.ts index 9411e76b..a0c110fc 100644 --- a/playwright/integrations.test.ts +++ b/playwright/integrations.test.ts @@ -63,8 +63,10 @@ test.describe("integrations", () => { body.payload.location = dynamic; for (const attendee of body.payload.attendees) { attendee.timeZone = dynamic; + attendee.language = dynamic; } body.payload.organizer.timeZone = dynamic; + body.payload.organizer.language = dynamic; body.payload.uid = dynamic; body.payload.additionInformation = dynamic; diff --git a/playwright/integrations.test.ts-snapshots/webhookResponse-chromium.txt b/playwright/integrations.test.ts-snapshots/webhookResponse-chromium.txt index 9851dbbe..c67a1932 100644 --- a/playwright/integrations.test.ts-snapshots/webhookResponse-chromium.txt +++ b/playwright/integrations.test.ts-snapshots/webhookResponse-chromium.txt @@ -1 +1 @@ -{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30min","title":"30min between Pro Example and Test Testson","description":"","startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"Pro Example","email":"pro@example.com","timeZone":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"uid":"[redacted/dynamic]","metadata":{},"additionInformation":"[redacted/dynamic]"}} \ No newline at end of file +{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30min","title":"30min between Pro Example and Test Testson","description":"","startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"Pro Example","email":"pro@example.com","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"uid":"[redacted/dynamic]","metadata":{},"additionInformation":"[redacted/dynamic]"}} \ No newline at end of file diff --git a/prisma/migrations/20220125035907_add_attendee_locale/migration.sql b/prisma/migrations/20220125035907_add_attendee_locale/migration.sql new file mode 100644 index 00000000..631c2f71 --- /dev/null +++ b/prisma/migrations/20220125035907_add_attendee_locale/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Attendee" ADD COLUMN "locale" TEXT DEFAULT E'en'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0fdf55e2..c75c4767 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -199,6 +199,7 @@ model Attendee { email String name String timeZone String + locale String? @default("en") booking Booking? @relation(fields: [bookingId], references: [id]) bookingId Int? } diff --git a/prisma/zod/attendee.ts b/prisma/zod/attendee.ts index 05e2bdcd..e49d49e8 100644 --- a/prisma/zod/attendee.ts +++ b/prisma/zod/attendee.ts @@ -8,6 +8,7 @@ export const _AttendeeModel = z.object({ email: z.string(), name: z.string(), timeZone: z.string(), + locale: z.string().nullish(), bookingId: z.number().int().nullish(), });