diff --git a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/reassign-bookings.e2e-spec.ts b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/reassign-bookings.e2e-spec.ts
index bf066fc6d3..d31ffd029b 100644
--- a/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/reassign-bookings.e2e-spec.ts
+++ b/apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/reassign-bookings.e2e-spec.ts
@@ -23,10 +23,13 @@ import { UserRepositoryFixture } from "test/fixtures/repository/users.repository
import { randomString } from "test/utils/randomString";
import { withApiAuth } from "test/utils/withApiAuth";
+
+
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
import type { CreateBookingInput_2024_08_13 } from "@calcom/platform-types";
import type { Booking, User, PlatformOAuthClient, Team } from "@calcom/prisma/client";
+
describe("Bookings Endpoints 2024-08-13", () => {
describe("Reassign bookings", () => {
let app: INestApplication;
@@ -47,10 +50,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
const teamUserEmail = `reassign-bookings-2024-08-13-user1-${randomString()}@api.com`;
const teamUserEmail2 = `reassign-bookings-2024-08-13-user2-${randomString()}@api.com`;
+ const teamUserEmail3 = `reassign-bookings-2024-08-13-user3-${randomString()}@api.com`;
let teamUser1: User;
let teamUser2: User;
+ let teamUser3: User;
let teamRoundRobinEventTypeId: number;
+ let teamRoundRobinFixedHostEventTypeId: number;
+ let teamRoundRobinNonFixedEventTypeId: number;
+
+ let teamRoundRobinNonFixedEventTypeTitle: string;
+ let teamRoundRobinFixedHostEventTypeTitle: string;
let roundRobinBooking: Booking;
@@ -96,23 +106,37 @@ describe("Bookings Endpoints 2024-08-13", () => {
teamUser1 = await userRepositoryFixture.create({
email: teamUserEmail,
- locale: "it",
+ locale: "en",
name: `reassign-bookings-2024-08-13-user1-${randomString()}`,
});
teamUser2 = await userRepositoryFixture.create({
email: teamUserEmail2,
- locale: "it",
+ locale: "en",
name: `reassign-bookings-2024-08-13-user2-${randomString()}`,
});
+ teamUser3 = await userRepositoryFixture.create({
+ email: teamUserEmail3,
+ locale: "en",
+ name: `reassign-bookings-2024-08-13-user3-${randomString()}`,
+ });
+
const userSchedule: CreateScheduleInput_2024_04_15 = {
name: `reassign-bookings-2024-08-13-schedule-${randomString()}`,
timeZone: "Europe/Rome",
isDefault: true,
+ availabilities: [
+ {
+ days: [0, 1, 2, 3, 4, 5, 6], // All days of the week
+ startTime: new Date(Date.UTC(2024, 0, 1, 0, 0, 0)), // 00:00 UTC
+ endTime: new Date(Date.UTC(2024, 0, 1, 23, 59, 0)), // 23:59 UTC
+ },
+ ],
};
await schedulesService.createUserSchedule(teamUser1.id, userSchedule);
await schedulesService.createUserSchedule(teamUser2.id, userSchedule);
+ await schedulesService.createUserSchedule(teamUser3.id, userSchedule);
await profileRepositoryFixture.create({
uid: `usr-${teamUser1.id}`,
@@ -144,6 +168,21 @@ describe("Bookings Endpoints 2024-08-13", () => {
},
});
+ await profileRepositoryFixture.create({
+ uid: `usr-${teamUser3.id}`,
+ username: teamUserEmail3,
+ organization: {
+ connect: {
+ id: organization.id,
+ },
+ },
+ user: {
+ connect: {
+ id: teamUser3.id,
+ },
+ },
+ });
+
await membershipsRepositoryFixture.create({
role: "MEMBER",
team: { connect: { id: team.id } },
@@ -158,6 +197,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
accepted: true,
});
+ await membershipsRepositoryFixture.create({
+ role: "MEMBER",
+ team: { connect: { id: team.id } },
+ user: { connect: { id: teamUser3.id } },
+ accepted: true,
+ });
+
const team1EventType = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "ROUND_ROBIN",
team: {
@@ -230,12 +276,119 @@ describe("Bookings Endpoints 2024-08-13", () => {
create: {
email: "bob@gmail.com",
name: "Bob",
- locale: "it",
+ locale: "en",
timeZone: "Europe/Rome",
},
},
});
+ const teamNonFixedEventType = await eventTypesRepositoryFixture.createTeamEventType({
+ schedulingType: "ROUND_ROBIN",
+ team: {
+ connect: { id: team.id },
+ },
+ users: {
+ connect: [{ id: teamUser2.id }, { id: teamUser3.id }],
+ },
+ title: `reassign-bookings-2024-08-13-non-fixed-event-type-${randomString()}`,
+ slug: `reassign-bookings-2024-08-13-non-fixed-event-type-${randomString()}`,
+ length: 60,
+ assignAllTeamMembers: false,
+ bookingFields: [],
+ locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
+ });
+
+ teamRoundRobinNonFixedEventTypeId = teamNonFixedEventType.id;
+ teamRoundRobinNonFixedEventTypeTitle = teamNonFixedEventType.title;
+
+ await hostsRepositoryFixture.create({
+ isFixed: false,
+ user: {
+ connect: {
+ id: teamUser2.id,
+ },
+ },
+ eventType: {
+ connect: {
+ id: teamNonFixedEventType.id,
+ },
+ },
+ });
+
+ await hostsRepositoryFixture.create({
+ isFixed: false,
+ user: {
+ connect: {
+ id: teamUser3.id,
+ },
+ },
+ eventType: {
+ connect: {
+ id: teamNonFixedEventType.id,
+ },
+ },
+ });
+ const team2EventType = await eventTypesRepositoryFixture.createTeamEventType({
+ schedulingType: "ROUND_ROBIN",
+ team: {
+ connect: { id: team.id },
+ },
+ users: {
+ connect: [{ id: teamUser1.id }, { id: teamUser2.id }, { id: teamUser3.id }],
+ },
+ title: `reassign-bookings-2024-08-13-fixed-event-type-${randomString()}`,
+ slug: `reassign-bookings-2024-08-13-fixed-event-type-${randomString()}`,
+ length: 60,
+ assignAllTeamMembers: false,
+ bookingFields: [],
+ locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
+ });
+
+ teamRoundRobinFixedHostEventTypeId = team2EventType.id;
+ teamRoundRobinFixedHostEventTypeTitle = team2EventType.title;
+
+ await hostsRepositoryFixture.create({
+ isFixed: true,
+ user: {
+ connect: {
+ id: teamUser1.id,
+ },
+ },
+ eventType: {
+ connect: {
+ id: team2EventType.id,
+ },
+ },
+ });
+
+ await hostsRepositoryFixture.create({
+ isFixed: false,
+ user: {
+ connect: {
+ id: teamUser2.id,
+ },
+ },
+ eventType: {
+ connect: {
+ id: team2EventType.id,
+ },
+ },
+ });
+
+ await hostsRepositoryFixture.create({
+ isFixed: false,
+ user: {
+ connect: {
+ id: teamUser3.id,
+ },
+ },
+ eventType: {
+ connect: {
+ id: team2EventType.id,
+ },
+ },
+ });
+
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
@@ -307,6 +460,104 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});
+ it("should have correct title when reassigning round robin booking with non-fixed host", async () => {
+ const nonFixedHostBookingBody: CreateBookingInput_2024_08_13 = {
+ start: new Date(Date.UTC(2050, 0, 9, 13, 0, 0)).toISOString(),
+ eventTypeId: teamRoundRobinNonFixedEventTypeId,
+ attendee: {
+ name: "Charlie",
+ email: "charlie@gmail.com",
+ timeZone: "Europe/Rome",
+ language: "en",
+ },
+ meetingUrl: "https://meet.google.com/abc-def-ghi",
+ };
+
+ const createResponse = await request(app.getHttpServer())
+ .post("/v2/bookings")
+ .send(nonFixedHostBookingBody)
+ .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
+ .expect(201);
+
+ const bookingUid = createResponse.body.data.uid;
+ const booking = await bookingsRepositoryFixture.getByUid(bookingUid);
+
+ expect(booking).toBeDefined();
+ expect(booking?.userId).toEqual(teamUser2.id);
+
+ const expectedInitialTitle = `${teamRoundRobinNonFixedEventTypeTitle} between ${teamUser2.name} and Charlie`;
+ expect(booking?.title).toEqual(expectedInitialTitle);
+
+ return request(app.getHttpServer())
+ .post(`/v2/bookings/${bookingUid}/reassign`)
+ .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
+ .expect(200)
+ .then(async (response) => {
+ const responseBody: ReassignBookingOutput_2024_08_13 = response.body;
+ expect(responseBody.status).toEqual(SUCCESS_STATUS);
+ expect(responseBody.data).toBeDefined();
+
+ const data: ReassignBookingOutput_2024_08_13["data"] = responseBody.data;
+ expect(data.bookingUid).toEqual(bookingUid);
+ expect(data.reassignedTo.id).toEqual(teamUser3.id);
+
+ const reassigned = await bookingsRepositoryFixture.getByUid(bookingUid);
+ expect(reassigned?.userId).toEqual(teamUser3.id);
+
+ const expectedReassignedTitle = `${teamRoundRobinNonFixedEventTypeTitle} between ${teamUser3.name} and Charlie`;
+ expect(reassigned?.title).toEqual(expectedReassignedTitle);
+ });
+ });
+
+ it("should have correct title when reassigning round robin booking with fixed host", async () => {
+ const fixedHostBookingBody: CreateBookingInput_2024_08_13 = {
+ start: new Date(Date.UTC(2050, 0, 8, 13, 0, 0)).toISOString(),
+ eventTypeId: teamRoundRobinFixedHostEventTypeId,
+ attendee: {
+ name: "Alice",
+ email: "alice@gmail.com",
+ timeZone: "Europe/Rome",
+ language: "en",
+ },
+ meetingUrl: "https://meet.google.com/abc-def-ghi",
+ };
+
+ const createResponse = await request(app.getHttpServer())
+ .post("/v2/bookings")
+ .send(fixedHostBookingBody)
+ .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
+ .expect(201);
+
+ const bookingUid = createResponse.body.data.uid;
+ const booking = await bookingsRepositoryFixture.getByUid(bookingUid);
+
+ expect(booking).toBeDefined();
+ expect(booking?.userId).toEqual(teamUser1.id);
+
+ const expectedInitialTitle = `${teamRoundRobinFixedHostEventTypeTitle} between ${team.name} and Alice`;
+ expect(booking?.title).toEqual(expectedInitialTitle);
+
+ return request(app.getHttpServer())
+ .post(`/v2/bookings/${bookingUid}/reassign/${teamUser3.id}`)
+ .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
+ .expect(200)
+ .then(async (response) => {
+ const responseBody: ReassignBookingOutput_2024_08_13 = response.body;
+ expect(responseBody.status).toEqual(SUCCESS_STATUS);
+ expect(responseBody.data).toBeDefined();
+
+ const data: ReassignBookingOutput_2024_08_13["data"] = responseBody.data;
+ expect(data.bookingUid).toEqual(bookingUid);
+ expect(data.reassignedTo.id).toEqual(teamUser1.id);
+
+ const reassigned = await bookingsRepositoryFixture.getByUid(bookingUid);
+ expect(reassigned?.userId).toEqual(teamUser1.id);
+
+ const expectedReassignedTitle = `${teamRoundRobinFixedHostEventTypeTitle} between ${team.name} and Alice`;
+ expect(reassigned?.title).toEqual(expectedReassignedTitle);
+ });
+ });
+
async function createOAuthClient(organizationId: number) {
const data = {
logo: "logo-url",
@@ -325,9 +576,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
await teamRepositoryFixture.delete(organization.id);
await userRepositoryFixture.deleteByEmail(teamUser1.email);
await userRepositoryFixture.deleteByEmail(teamUserEmail2);
+ await userRepositoryFixture.deleteByEmail(teamUserEmail3);
await bookingsRepositoryFixture.deleteAllBookings(teamUser1.id, teamUser1.email);
await bookingsRepositoryFixture.deleteAllBookings(teamUser2.id, teamUser2.email);
+ await bookingsRepositoryFixture.deleteAllBookings(teamUser3.id, teamUser3.email);
await app.close();
});
});
-});
+});
\ No newline at end of file
diff --git a/docs/api-reference/v2/openapi.json b/docs/api-reference/v2/openapi.json
index 9436e898cb..09fc879188 100644
--- a/docs/api-reference/v2/openapi.json
+++ b/docs/api-reference/v2/openapi.json
@@ -7957,7 +7957,7 @@
"post": {
"operationId": "BookingsController_2024_08_13_createBooking",
"summary": "Create a booking",
- "description": "\n POST /v2/bookings is used to create regular bookings, recurring bookings and instant bookings. The request bodies for all 3 are almost the same except:\n If eventTypeId in the request body is id of a regular event, then regular booking is created.\n\n If it is an id of a recurring event type, then recurring booking is created.\n\n Meaning that the request bodies are equal but the outcome depends on what kind of event type it is with the goal of making it as seamless for developers as possible.\n\n For team event types it is possible to create instant meeting. To do that just pass `\"instant\": true` to the request body.\n\n The start needs to be in UTC aka if the timezone is GMT+2 in Rome and meeting should start at 11, then UTC time should have hours 09:00 aka without time zone.\n\n Finally, there are 2 ways to book an event type belonging to an individual user:\n 1. Provide `eventTypeId` in the request body.\n 2. Provide `eventTypeSlug` and `username` and optionally `organizationSlug` if the user with the username is within an organization.\n\n And 2 ways to book and event type belonging to a team:\n 1. Provide `eventTypeId` in the request body.\n 2. Provide `eventTypeSlug` and `teamSlug` and optionally `organizationSlug` if the team with the teamSlug is within an organization.\n\n If you are creating a seated booking for an event type with 'show attendees' disabled, then to retrieve attendees in the response either set 'show attendees' to true on event type level or\n you have to provide an authentication method of event type owner, host, team admin or owner or org admin or owner.\n\n Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.\n ",
+ "description": "\n POST /v2/bookings is used to create regular bookings, recurring bookings and instant bookings. The request bodies for all 3 are almost the same except:\n If eventTypeId in the request body is id of a regular event, then regular booking is created.\n\n If it is an id of a recurring event type, then recurring booking is created.\n\n Meaning that the request bodies are equal but the outcome depends on what kind of event type it is with the goal of making it as seamless for developers as possible.\n\n For team event types it is possible to create instant meeting. To do that just pass `\"instant\": true` to the request body.\n\n The start needs to be in UTC aka if the timezone is GMT+2 in Rome and meeting should start at 11, then UTC time should have hours 09:00 aka without time zone.\n\n Finally, there are 2 ways to book an event type belonging to an individual user:\n 1. Provide `eventTypeId` in the request body.\n 2. Provide `eventTypeSlug` and `username` and optionally `organizationSlug` if the user with the username is within an organization.\n\n And 2 ways to book and event type belonging to a team:\n 1. Provide `eventTypeId` in the request body.\n 2. Provide `eventTypeSlug` and `teamSlug` and optionally `organizationSlug` if the team with the teamSlug is within an organization.\n\n If you are creating a seated booking for an event type with 'show attendees' disabled, then to retrieve attendees in the response either set 'show attendees' to true on event type level or\n you have to provide an authentication method of event type owner, host, team admin or owner or org admin or owner.\n\n For event types that have SMS reminders workflow, you need to pass the attendee's phone number in the request body via `attendee.phoneNumber` (e.g., \"+19876543210\" in international format). This is an optional field, but becomes required when SMS reminders are enabled for the event type. For the complete attendee object structure, see the [attendee object](https://cal.com/docs/api-reference/v2/bookings/create-a-booking#body-attendee) documentation.\n\n Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.\n ",
"parameters": [
{
"name": "cal-api-version",
@@ -8518,7 +8518,7 @@
"post": {
"operationId": "BookingsController_2024_08_13_cancelBooking",
"summary": "Cancel a booking",
- "description": ":bookingUid can be :bookingUid of an usual booking, individual recurrence or recurring booking to cancel all recurrences.\n \n \nCancelling normal bookings:\n If the booking is not seated and not recurring simply pass :bookingUid in the request URL `/bookings/:bookingUid/cancel` and optionally cancellationReason in the request body `{\"cancellationReason\": \"Will travel\"}`.\n\n \nCancelling seated bookings:\n It is possible to cancel specific seat within a booking as an attendee or all of the seats as the host.\n \n1. As an attendee - provide :bookingUid in the request URL `/bookings/:bookingUid/cancel` and seatUid in the request body `{\"seatUid\": \"123-123-123\"}` . This will remove this particular attendance from the booking.\n \n2. As the host - host can cancel booking for all attendees aka for every seat. Provide :bookingUid in the request URL `/bookings/:bookingUid/cancel` and cancellationReason in the request body `{\"cancellationReason\": \"Will travel\"}` and `Authorization: Bearer token` request header where token is event type owner (host) credential. This will cancel the booking for all attendees.\n \n \nCancelling recurring seated bookings:\n For recurring seated bookings it is not possible to cancel all of them with 1 call\n like with non-seated recurring bookings by providing recurring bookind uid - you have to cancel each recurrence booking by its bookingUid + seatUid.\n \n If you are cancelling a seated booking for an event type with 'show attendees' disabled, then to retrieve attendees in the response either set 'show attendees' to true on event type level or\n you have to provide an authentication method of event type owner, host, team admin or owner or org admin or owner.\n\n Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.\n ",
+ "description": ":bookingUid can be :bookingUid of an usual booking, individual recurrence or recurring booking to cancel all recurrences.\n \n \nCancelling normal bookings:\n If the booking is not seated and not recurring, simply pass :bookingUid in the request URL `/bookings/:bookingUid/cancel` and optionally cancellationReason in the request body `{\"cancellationReason\": \"Will travel\"}`.\n\n \nCancelling seated bookings:\n It is possible to cancel specific seat within a booking as an attendee or all of the seats as the host.\n \n1. As an attendee - provide :bookingUid in the request URL `/bookings/:bookingUid/cancel` and seatUid in the request body `{\"seatUid\": \"123-123-123\"}` . This will remove this particular attendance from the booking.\n \n2. As the host - host can cancel booking for all attendees aka for every seat. Provide :bookingUid in the request URL `/bookings/:bookingUid/cancel` and cancellationReason in the request body `{\"cancellationReason\": \"Will travel\"}` and `Authorization: Bearer token` request header where token is event type owner (host) credential. This will cancel the booking for all attendees.\n \n \nCancelling recurring seated bookings:\n For recurring seated bookings it is not possible to cancel all of them with 1 call\n like with non-seated recurring bookings by providing recurring bookind uid - you have to cancel each recurrence booking by its bookingUid + seatUid.\n \n If you are cancelling a seated booking for an event type with 'show attendees' disabled, then to retrieve attendees in the response either set 'show attendees' to true on event type level or\n you have to provide an authentication method of event type owner, host, team admin or owner or org admin or owner.\n\n Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.\n ",
"parameters": [
{
"name": "cal-api-version",
diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts
index ea0a3e8717..759e532aa0 100644
--- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts
+++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts
@@ -1,6 +1,7 @@
-
import { cloneDeep } from "lodash";
+
+
import { enrichUserWithDelegationCredentialsIncludeServiceAccountKey } from "@calcom/app-store/delegationCredential";
import { eventTypeAppMetadataOptionalSchema } from "@calcom/app-store/zod-utils";
import dayjs from "@calcom/dayjs";
@@ -37,12 +38,15 @@ import { WorkflowActions, WorkflowMethods, WorkflowTriggerEvents } from "@calcom
import type { EventTypeMetadata, PlatformClientParams } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
+
+
import { handleRescheduleEventManager } from "./handleRescheduleEventManager";
import type { BookingSelectResult } from "./utils/bookingSelect";
import { bookingSelect } from "./utils/bookingSelect";
import { getDestinationCalendar } from "./utils/getDestinationCalendar";
import { getTeamMembers } from "./utils/getTeamMembers";
+
enum ErrorCode {
InvalidRoundRobinHost = "invalid_round_robin_host",
UserIsFixed = "user_is_round_robin_fixed",
@@ -151,6 +155,17 @@ export const roundRobinManualReassignment = async ({
const previousRRHostT = await getTranslation(previousRRHost?.locale || "en", "common");
let bookingLocation = booking.location;
let conferenceCredentialId: number | null = null;
+
+ const organizer = hasOrganizerChanged ? newUser : booking.user ?? newUser;
+
+ const teamMembers = await getTeamMembers({
+ eventTypeHosts,
+ attendees: booking.attendees,
+ organizer,
+ previousHost: previousRRHost || null,
+ reassignedHost: newUser,
+ });
+
if (hasOrganizerChanged) {
const bookingResponses = booking.responses;
const responseSchema = getBookingResponsesSchema({
@@ -180,7 +195,8 @@ export const roundRobinManualReassignment = async ({
attendeeName: responses?.name || "Nameless",
eventType: eventType.title,
eventName: eventType.eventName,
- teamName: eventType.team?.name,
+ // we send on behalf of team if >1 round robin attendee | collective
+ teamName: teamMembers.length > 1 ? eventType.team?.name : null,
host: newUser.name || "Nameless",
location: bookingLocation || "integrations:daily",
bookingFields: { ...responses },
@@ -244,18 +260,8 @@ export const roundRobinManualReassignment = async ({
hasOrganizerChanged,
});
- const organizer = hasOrganizerChanged ? newUser : booking.user ?? newUser;
-
const organizerT = await getTranslation(organizer?.locale || "en", "common");
- const teamMembers = await getTeamMembers({
- eventTypeHosts,
- attendees: booking.attendees,
- organizer,
- previousHost: previousRRHost || null,
- reassignedHost: newUser,
- });
-
const attendeePromises = [];
for (const attendee of booking.attendees) {
if (
@@ -312,7 +318,7 @@ export const roundRobinManualReassignment = async ({
conferenceCredentialId: conferenceCredentialId ?? undefined,
};
- if( hasOrganizerChanged ){
+ if (hasOrganizerChanged) {
// location might changed and will be new created in eventManager.create (organizer default location)
evt.videoCallData = undefined;
// To prevent "The requested identifier already exists" error while updating event, we need to remove iCalUID
@@ -614,4 +620,4 @@ export async function handleWorkflowsUpdate({
});
}
-export default roundRobinManualReassignment;
+export default roundRobinManualReassignment;
\ No newline at end of file
diff --git a/packages/platform/libraries/i18n.ts b/packages/platform/libraries/i18n.ts
index 1f993ecd8b..4236f49e9c 100644
--- a/packages/platform/libraries/i18n.ts
+++ b/packages/platform/libraries/i18n.ts
@@ -1,14 +1,19 @@
import { createInstance } from "i18next";
+import type { i18n as I18nInstance } from "i18next";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { fetchWithTimeout } from "@calcom/lib/fetchWithTimeout";
import logger from "@calcom/lib/logger";
-/* eslint-disable @typescript-eslint/no-var-requires */
+/* eslint-disable @typescript-eslint/no-require-imports */
const { i18n } = require("@calcom/config/next-i18next.config");
+const path = require("path");
+const translationsPath = path.resolve(__dirname, "../../../../apps/web/public/static/locales/en/common.json");
+const englishTranslations: Record = require(translationsPath);
+/* eslint-enable @typescript-eslint/no-require-imports */
-const translationCache = new Map>();
-const i18nInstanceCache = new Map();
+const translationCache = new Map>([["en-common", englishTranslations]]);
+const i18nInstanceCache = new Map();
const SUPPORTED_NAMESPACES = ["common"];
/**
@@ -38,17 +43,16 @@ export async function loadTranslations(_locale: string, _ns: string) {
);
if (!response.ok) {
- logger.error(`Failed to fetch translations: ${response.status}`);
- return {};
+ logger.warn(`Failed to fetch translations for ${locale}: ${response.status}, falling back to English`);
+ return englishTranslations;
}
const translations = await response.json();
translationCache.set(cacheKey, translations);
return translations;
} catch (err) {
- console.error("loadTranslations Error:", err);
-
- return {};
+ logger.warn(`Failed to load translations for ${locale}, falling back to English:`, err);
+ return englishTranslations;
}
}
@@ -60,8 +64,9 @@ export async function loadTranslations(_locale: string, _ns: string) {
*/
export const getTranslation = async (locale: string, ns: string) => {
const cacheKey = `${locale}-${ns}`;
- if (i18nInstanceCache.has(cacheKey)) {
- return i18nInstanceCache.get(cacheKey).getFixedT(locale, ns);
+ const cachedInstance = i18nInstanceCache.get(cacheKey);
+ if (cachedInstance) {
+ return cachedInstance.getFixedT(locale, ns);
}
const resources = await loadTranslations(locale, ns);