feat: add toggle to opt out of booking title translation in instant meetings (#25547)

* feat: add toggle to opt out of booking title translation in instant meetings

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: add autoTranslateTitleEnabled to test builder

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: respect autoTranslateTitleEnabled toggle in InstantBookingCreateService

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: fetch autoTranslateTitleEnabled directly in InstantBookingCreateService

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* refactor: rename autoTranslateTitleEnabled to autoTranslateInstantMeetingTitleEnabled

- Renamed field to clarify it only applies to instant meeting title translation
- Moved Prisma query to EventTypeRepository.findInstantMeetingConfigById()
- Removed title translation logic from update.handler.ts (flag only controls instant meeting title)
- Updated all references across the codebase
- Added new i18n translation keys for the renamed field

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: add autoTranslateInstantMeetingTitleEnabled to test destructuring patterns

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: use Prisma.TeamCreateInput type for metadata in test helper

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: add autoTranslateInstantMeetingTitleEnabled to mockUpdatedEventType in test

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* refactor: use generic findByIdMinimal instead of business-specific method

- Add autoTranslateInstantMeetingTitleEnabled to eventTypeSelect constant
- Remove findInstantMeetingConfigById from EventTypeRepository
- Update InstantBookingCreateService to use findByIdMinimal

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* refactor: remove autoTranslateInstantMeetingTitleEnabled from eventTypeSelect (not needed for findByIdMinimal)

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* feat: add autoTranslateInstantMeetingTitleEnabled to event type form defaults

- Rename shouldTranslateTitle to shouldAutoTranslateInstantMeetingTitle for clarity
- Add autoTranslateInstantMeetingTitleEnabled to findById and findByIdForOrgAdmin selects
- Add autoTranslateInstantMeetingTitleEnabled to useEventTypeForm defaultValues

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

* fix: only update autoTranslateInstantMeetingTitleEnabled when explicitly provided

Fixes issue where omitting the field in update requests would overwrite
the saved opt-out setting with the default value (true). Now the field
is only included in the update payload when explicitly provided.

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2025-12-09 13:51:51 +00:00
1 parent 6ae2ce8ed9
commit 3a59cd41cc
14 files changed
+57 -17

No files matched your search

@@ -181,6 +181,8 @@ const getEventTypesFromDBSelect = {
name: true,
},
},
instantMeetingExpiryTimeOffsetInSeconds: true,
autoTranslateInstantMeetingTitleEnabled: true,
} satisfies Prisma.EventTypeSelect;
export const getEventTypesFromDB = async (eventTypeId: number) => {
@@ -197,9 +197,19 @@ export async function handler(
const attendeeTimezone = reqBody.timeZone;
const attendeeLanguage = reqBody.language;
const tAttendees = await getTranslation(attendeeLanguage ?? "en", "common");
const tEnglish = await getTranslation("en", "common");
const fullName = getFullName(bookerName);
// Determine whether to auto-translate the instant meeting title based on the event type setting
// Default is true (opt-out), so we only skip translation when explicitly set to false
const shouldAutoTranslateInstantMeetingTitle = eventType.autoTranslateInstantMeetingTitleEnabled;
// Get the booking title - either translated to attendee's language or in English
const bookingTitle = shouldAutoTranslateInstantMeetingTitle
? tAttendees("instant_meeting_with_title", { name: fullName })
: tEnglish("instant_meeting_with_title", { name: fullName });
const invitee = [
{
email: bookerEmail,
@@ -242,7 +252,7 @@ export async function handler(
const newBookingData: Prisma.BookingCreateInput = {
uid,
responses: reqBody.responses === null ? Prisma.JsonNull : reqBody.responses,
title: tAttendees("instant_meeting_with_title", { name: invitee[0].name }),
title: bookingTitle,
startTime: dayjs.utc(reqBody.start).toDate(),
endTime: dayjs.utc(reqBody.end).toDate(),
description: reqBody.notes,
@@ -279,17 +289,7 @@ export async function handler(
const token = randomBytes(32).toString("hex");
const eventTypeWithExpiryTimeOffset = await prisma.eventType.findUniqueOrThrow({
where: {
id: bookingData.eventTypeId,
},
select: {
instantMeetingExpiryTimeOffsetInSeconds: true,
},
});
const instantMeetingExpiryTimeOffsetInSeconds =
eventTypeWithExpiryTimeOffset?.instantMeetingExpiryTimeOffsetInSeconds ?? 90;
const instantMeetingExpiryTimeOffsetInSeconds = eventType.instantMeetingExpiryTimeOffsetInSeconds ?? 90;
const instantMeetingToken = await prisma.instantMeetingToken.create({
data: {
@@ -245,6 +245,21 @@ export default function InstantEventController({
</>
)}
/>
<Controller
name="autoTranslateInstantMeetingTitleEnabled"
render={({ field: { value, onChange } }) => (
<SettingsToggle
labelClassName="text-sm"
title={t("auto_translate_instant_meeting_title")}
checked={value}
onCheckedChange={(e) => onChange(e)}
data-testid="auto-translate-instant-meeting-title-toggle"
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6 mt-4"
description={t("auto_translate_instant_meeting_title_description")}
/>
)}
/>
<InstantMeetingWebhooks eventType={eventType} />
</div>
)}
@@ -145,6 +145,7 @@ const commons = {
requiresConfirmationWillBlockSlot: false,
canSendCalVideoTranscriptionEmails: false,
instantMeetingExpiryTimeOffsetInSeconds: 0,
autoTranslateInstantMeetingTitleEnabled: true,
instantMeetingScheduleId: null,
instantMeetingParameters: [],
eventTypeColor: null,
@@ -135,6 +135,7 @@ export type FormValues = {
seatsShowAvailabilityCount: boolean | null;
seatsPerTimeSlotEnabled: boolean;
autoTranslateDescriptionEnabled: boolean;
autoTranslateInstantMeetingTitleEnabled: boolean;
fieldTranslations: EventTypeTranslation[];
scheduleName: string;
minimumBookingNotice: number;
@@ -570,6 +570,7 @@ export class EventTypeRepository {
requiresConfirmationWillBlockSlot: true,
requiresBookerEmailVerification: true,
autoTranslateDescriptionEnabled: true,
autoTranslateInstantMeetingTitleEnabled: true,
fieldTranslations: {
select: {
translatedText: true,
@@ -868,6 +869,7 @@ export class EventTypeRepository {
requiresConfirmationWillBlockSlot: true,
requiresBookerEmailVerification: true,
autoTranslateDescriptionEnabled: true,
autoTranslateInstantMeetingTitleEnabled: true,
fieldTranslations: {
select: {
translatedText: true,