fix: translate location dropdown group labels and options in event-type settings (#26784)
* fix: translate location dropdown group labels in event-type settings Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: use requesting user's locale for team event type translations For team event types, the current user might not be in eventType.users, causing translations to default to English. This fix queries the user's locale directly from the database when not found in the event type users. Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor: pass userLocale as parameter instead of database call Per PR feedback, pass the authenticated user's locale as an optional parameter to getEventTypeById instead of making an extra database call. The locale is passed from ctx.user.locale in the tRPC handler. Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: provide userLocale in apiv2 getEventTypeById * fix: remove unused ts comments --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
morgan@cal.com <morgan@cal.com>
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
a7a8c78494
commit
0b37520f0d
@@ -1,12 +1,11 @@
|
||||
import { getEventTypeById } from "@calcom/platform-libraries/event-types";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { getEventTypeById } from "@calcom/platform-libraries/event-types";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
@Injectable()
|
||||
export class EventTypesRepository_2024_04_15 {
|
||||
@@ -54,6 +53,7 @@ export class EventTypesRepository_2024_04_15 {
|
||||
currentOrganizationId: this.usersService.getUserMainOrgId(user),
|
||||
eventTypeId,
|
||||
userId: user.id,
|
||||
userLocale: user.locale ?? "en",
|
||||
prisma: this.dbRead.prisma as unknown as PrismaClient,
|
||||
isUserOrganizationAdmin,
|
||||
isTrpcCall: true,
|
||||
|
||||
@@ -1,3 +1,32 @@
|
||||
import type { TeamQuery } from "@calcom/platform-libraries";
|
||||
import { checkAdminOrOwner, getClientSecretFromPayment } from "@calcom/platform-libraries";
|
||||
import type {
|
||||
App,
|
||||
CredentialDataWithTeamName,
|
||||
CredentialOwner,
|
||||
CredentialPayload,
|
||||
LocationOption,
|
||||
TDependencyData,
|
||||
} from "@calcom/platform-libraries/app-store";
|
||||
import {
|
||||
enrichUserWithDelegationConferencingCredentialsWithoutOrgId,
|
||||
getAppFromSlug,
|
||||
getEnabledAppsFromCredentials,
|
||||
} from "@calcom/platform-libraries/app-store";
|
||||
import {
|
||||
bulkUpdateEventsToDefaultLocation,
|
||||
bulkUpdateTeamEventsToDefaultLocation,
|
||||
EventTypeMetaDataSchema,
|
||||
getBulkTeamEventTypes,
|
||||
getBulkUserEventTypes,
|
||||
getEventTypeById,
|
||||
getPublicEvent,
|
||||
type PublicEventType,
|
||||
TUpdateEventTypeInputSchema,
|
||||
updateEventType,
|
||||
} from "@calcom/platform-libraries/event-types";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
|
||||
import { systemBeforeFieldEmail } from "@/ee/event-types/event-types_2024_06_14/transformers";
|
||||
import { AtomsRepository } from "@/modules/atoms/atoms.repository";
|
||||
@@ -9,34 +38,6 @@ import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { TeamsEventTypesService } from "@/modules/teams/event-types/services/teams-event-types.service";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable, NotFoundException, ForbiddenException, BadRequestException } from "@nestjs/common";
|
||||
|
||||
import { checkAdminOrOwner, getClientSecretFromPayment } from "@calcom/platform-libraries";
|
||||
import type { TeamQuery } from "@calcom/platform-libraries";
|
||||
import { enrichUserWithDelegationConferencingCredentialsWithoutOrgId } from "@calcom/platform-libraries/app-store";
|
||||
import { getEnabledAppsFromCredentials, getAppFromSlug } from "@calcom/platform-libraries/app-store";
|
||||
import type {
|
||||
App,
|
||||
TDependencyData,
|
||||
CredentialOwner,
|
||||
CredentialPayload,
|
||||
CredentialDataWithTeamName,
|
||||
LocationOption,
|
||||
} from "@calcom/platform-libraries/app-store";
|
||||
import { type PublicEventType, getPublicEvent } from "@calcom/platform-libraries/event-types";
|
||||
import {
|
||||
getEventTypeById,
|
||||
bulkUpdateEventsToDefaultLocation,
|
||||
bulkUpdateTeamEventsToDefaultLocation,
|
||||
getBulkUserEventTypes,
|
||||
getBulkTeamEventTypes,
|
||||
} from "@calcom/platform-libraries/event-types";
|
||||
import {
|
||||
updateEventType,
|
||||
TUpdateEventTypeInputSchema,
|
||||
EventTypeMetaDataSchema,
|
||||
} from "@calcom/platform-libraries/event-types";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
type EnabledAppType = App & {
|
||||
credential: CredentialDataWithTeamName;
|
||||
@@ -81,6 +82,7 @@ export class EventTypesAtomService {
|
||||
currentOrganizationId: this.usersService.getUserMainOrgId(user),
|
||||
eventTypeId,
|
||||
userId: user.id,
|
||||
userLocale: user.locale ?? "en",
|
||||
prisma: this.dbRead.prisma as unknown as PrismaClient,
|
||||
isUserOrganizationAdmin,
|
||||
isTrpcCall: true,
|
||||
@@ -136,8 +138,6 @@ export class EventTypesAtomService {
|
||||
input: { ...body, id: eventTypeId, bookingFields },
|
||||
ctx: {
|
||||
user: eventTypeUser,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
prisma: this.dbWrite.prisma,
|
||||
},
|
||||
});
|
||||
@@ -166,8 +166,6 @@ export class EventTypesAtomService {
|
||||
input: { ...body, id: eventTypeId, bookingFields },
|
||||
ctx: {
|
||||
user: eventTypeUser,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
prisma: this.dbWrite.prisma,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -739,6 +739,7 @@
|
||||
"booking_reschedule_confirmation": "Reschedule your {{eventTypeTitle}} with {{profileName}}",
|
||||
"in_person_meeting": "In-person meeting",
|
||||
"in_person": "In Person (Organizer Address)",
|
||||
"in_person_category": "In person",
|
||||
"link_meeting": "Link meeting",
|
||||
"phone_number": "Phone number",
|
||||
"attendee_phone_number": "Attendee phone number",
|
||||
|
||||
@@ -719,6 +719,7 @@
|
||||
"booking_reschedule_confirmation": "Replanifiez votre {{eventTypeTitle}} avec {{profileName}}",
|
||||
"in_person_meeting": "Rendez-vous en personne",
|
||||
"in_person": "En personne (adresse de l'organisateur)",
|
||||
"in_person_category": "En personne",
|
||||
"link_meeting": "Rendez-vous avec lien",
|
||||
"phone_number": "Numéro de téléphone",
|
||||
"attendee_phone_number": "Numéro de téléphone du participant",
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
/**
|
||||
* TODO: Consolidate this file with BookingLocationService and add tests
|
||||
*/
|
||||
import type { TFunction } from "i18next";
|
||||
import { isValidPhoneNumber } from "libphonenumber-js/max";
|
||||
import { z } from "zod";
|
||||
|
||||
import { appStoreMetadata } from "@calcom/app-store/bookerAppsMetaData";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import type { Ensure, Optional } from "@calcom/types/utils";
|
||||
import type { TFunction } from "i18next";
|
||||
import { isValidPhoneNumber } from "libphonenumber-js/max";
|
||||
import { z } from "zod";
|
||||
|
||||
import type { EventLocationTypeFromAppMeta } from "../types/App";
|
||||
import {
|
||||
DailyLocationType as importedDailyLocationType,
|
||||
MeetLocationType as importedMeetLocationType,
|
||||
MSTeamsLocationType as importedMSTeamsLocationType,
|
||||
DailyLocationType as importedDailyLocationType,
|
||||
} from "./constants";
|
||||
|
||||
export const MeetLocationType = importedMeetLocationType;
|
||||
@@ -26,7 +26,7 @@ export type DefaultEventLocationType = {
|
||||
type: DefaultEventLocationTypeEnum;
|
||||
label: string;
|
||||
messageForOrganizer: string;
|
||||
category: "in person" | "conferencing" | "other" | "phone";
|
||||
category: "in_person_category" | "conferencing" | "other" | "phone";
|
||||
linkType: "static";
|
||||
supportsCustomLabel?: boolean;
|
||||
|
||||
@@ -114,7 +114,7 @@ export const defaultLocations: DefaultEventLocationType[] = [
|
||||
attendeeInputPlaceholder: "enter_address",
|
||||
defaultValueVariable: "attendeeAddress",
|
||||
iconUrl: "/map-pin-dark.svg",
|
||||
category: "in person",
|
||||
category: "in_person_category",
|
||||
linkType: "static",
|
||||
supportsCustomLabel: true,
|
||||
},
|
||||
@@ -143,7 +143,7 @@ export const defaultLocations: DefaultEventLocationType[] = [
|
||||
variable: "locationAddress",
|
||||
defaultValueVariable: "address",
|
||||
iconUrl: "/map-pin-dark.svg",
|
||||
category: "in person",
|
||||
category: "in_person_category",
|
||||
linkType: "static",
|
||||
},
|
||||
{
|
||||
@@ -242,7 +242,7 @@ for (const [appName, meta] of Object.entries(appStoreMetadata)) {
|
||||
for (const [key, value] of Object.entries(location)) {
|
||||
if (typeof value === "string") {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
location[key] = value.replace(/{SLUG}/g, meta.slug).replace(/{TITLE}/g, meta.name);
|
||||
}
|
||||
}
|
||||
@@ -397,7 +397,7 @@ export const getLocationValueForDB = (
|
||||
eventLocations: LocationObject[]
|
||||
) => {
|
||||
let bookingLocation = bookingLocationTypeOrValue;
|
||||
let conferenceCredentialId: number | undefined = undefined;
|
||||
let conferenceCredentialId: number | undefined;
|
||||
|
||||
eventLocations.forEach((location) => {
|
||||
if (location.type === bookingLocationTypeOrValue) {
|
||||
@@ -481,8 +481,8 @@ export const getTranslatedLocation = (
|
||||
const translatedLocation = location.type.startsWith("integrations:")
|
||||
? eventLocationType.label
|
||||
: translateAbleKeys.includes(locationKey)
|
||||
? t(locationKey)
|
||||
: locationKey;
|
||||
? t(locationKey)
|
||||
: locationKey;
|
||||
|
||||
return translatedLocation;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,9 @@ import { getLocationGroupedOptions } from "@calcom/app-store/server";
|
||||
import { getEventTypeAppData } from "@calcom/app-store/utils";
|
||||
import { eventTypeMetaDataSchemaWithTypedApps } from "@calcom/app-store/zod-utils";
|
||||
import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields";
|
||||
import { getOrganizationRepository } from "@calcom/features/ee/organizations/di/OrganizationRepository.container";
|
||||
import { getBookerBaseUrl } from "@calcom/features/ee/organizations/lib/getBookerUrlServer";
|
||||
import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
|
||||
import { EventTypeRepository } from "@calcom/features/eventtypes/repositories/eventTypeRepository";
|
||||
import { UserRepository } from "@calcom/features/users/repositories/UserRepository";
|
||||
import { WEBSITE_URL } from "@calcom/lib/constants";
|
||||
@@ -16,11 +18,9 @@ import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { SchedulingType, MembershipRole } from "@calcom/prisma/enums";
|
||||
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
|
||||
import { customInputSchema } from "@calcom/prisma/zod-utils";
|
||||
import { OrganizationRepository } from "@calcom/features/ee/organizations/repositories/OrganizationRepository";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { getOrganizationRepository } from "@calcom/features/ee/organizations/di/OrganizationRepository.container";
|
||||
|
||||
interface getEventTypeByIdProps {
|
||||
eventTypeId: number;
|
||||
@@ -29,6 +29,7 @@ interface getEventTypeByIdProps {
|
||||
isTrpcCall?: boolean;
|
||||
isUserOrganizationAdmin: boolean;
|
||||
currentOrganizationId: number | null;
|
||||
userLocale?: string | null;
|
||||
}
|
||||
|
||||
export type EventType = Awaited<ReturnType<typeof getEventTypeById>>;
|
||||
@@ -40,6 +41,7 @@ export const getEventTypeById = async ({
|
||||
prisma,
|
||||
isTrpcCall = false,
|
||||
isUserOrganizationAdmin,
|
||||
userLocale,
|
||||
}: getEventTypeByIdProps) => {
|
||||
const userSelect = {
|
||||
name: true,
|
||||
@@ -58,7 +60,7 @@ export const getEventTypeById = async ({
|
||||
eventTypeId,
|
||||
isUserOrganizationAdmin,
|
||||
currentOrganizationId,
|
||||
prisma
|
||||
prisma,
|
||||
});
|
||||
|
||||
if (!rawEventType) {
|
||||
@@ -90,8 +92,8 @@ export const getEventTypeById = async ({
|
||||
...child,
|
||||
owner: child.owner
|
||||
? await userRepo.enrichUserWithItsProfile({
|
||||
user: child.owner,
|
||||
})
|
||||
user: child.owner,
|
||||
})
|
||||
: null,
|
||||
});
|
||||
}
|
||||
@@ -141,19 +143,19 @@ export const getEventTypeById = async ({
|
||||
children: childrenWithUserProfile.flatMap((ch) =>
|
||||
ch.owner !== null
|
||||
? {
|
||||
...ch,
|
||||
owner: {
|
||||
...ch.owner,
|
||||
avatar: getUserAvatarUrl(ch.owner),
|
||||
email: ch.owner.email,
|
||||
name: ch.owner.name ?? "",
|
||||
username: ch.owner.username ?? "",
|
||||
membership:
|
||||
restEventType.team?.members.find((tm) => tm.user.id === ch.owner?.id)?.role ||
|
||||
MembershipRole.MEMBER,
|
||||
},
|
||||
created: true,
|
||||
}
|
||||
...ch,
|
||||
owner: {
|
||||
...ch.owner,
|
||||
avatar: getUserAvatarUrl(ch.owner),
|
||||
email: ch.owner.email,
|
||||
name: ch.owner.name ?? "",
|
||||
username: ch.owner.username ?? "",
|
||||
membership:
|
||||
restEventType.team?.members.find((tm) => tm.user.id === ch.owner?.id)?.role ||
|
||||
MembershipRole.MEMBER,
|
||||
},
|
||||
created: true,
|
||||
}
|
||||
: []
|
||||
),
|
||||
};
|
||||
@@ -187,7 +189,7 @@ export const getEventTypeById = async ({
|
||||
|
||||
const currentUser = eventType.users.find((u) => u.id === userId);
|
||||
|
||||
const t = await getTranslation(currentUser?.locale ?? "en", "common");
|
||||
const t = await getTranslation(userLocale ?? currentUser?.locale ?? "en", "common");
|
||||
|
||||
if (!currentUser?.id && !eventType.teamId) {
|
||||
throw new TRPCError({
|
||||
@@ -224,19 +226,19 @@ export const getEventTypeById = async ({
|
||||
const isOrgEventType = !!eventTypeObject.team?.parentId;
|
||||
const teamMembers = eventTypeObject.team
|
||||
? eventTeamMembershipsWithUserProfile
|
||||
.filter((member) => member.accepted || isOrgEventType)
|
||||
.map((member) => {
|
||||
const user: typeof member.user & { avatar: string } = {
|
||||
...member.user,
|
||||
avatar: getUserAvatarUrl(member.user),
|
||||
};
|
||||
return {
|
||||
...user,
|
||||
profileId: user.profile.id,
|
||||
eventTypes: user.eventTypes.map((evTy) => evTy.slug),
|
||||
membership: member.role,
|
||||
};
|
||||
})
|
||||
.filter((member) => member.accepted || isOrgEventType)
|
||||
.map((member) => {
|
||||
const user: typeof member.user & { avatar: string } = {
|
||||
...member.user,
|
||||
avatar: getUserAvatarUrl(member.user),
|
||||
};
|
||||
return {
|
||||
...user,
|
||||
profileId: user.profile.id,
|
||||
eventTypes: user.eventTypes.map((evTy) => evTy.slug),
|
||||
membership: member.role,
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
// Find the current users membership so we can check role to enable/disable deletion.
|
||||
@@ -274,7 +276,9 @@ export async function getRawEventType({
|
||||
}: Omit<getEventTypeByIdProps, "isTrpcCall">) {
|
||||
const eventTypeRepo = new EventTypeRepository(prisma);
|
||||
const organizationRepo = getOrganizationRepository();
|
||||
const isUserInPlatformOrganization = currentOrganizationId ? !!(await organizationRepo.findById({ id: currentOrganizationId }))?.isPlatform : false;
|
||||
const isUserInPlatformOrganization = currentOrganizationId
|
||||
? !!(await organizationRepo.findById({ id: currentOrganizationId }))?.isPlatform
|
||||
: false;
|
||||
|
||||
if (isUserOrganizationAdmin && currentOrganizationId && isUserInPlatformOrganization) {
|
||||
// Platform Organization Admin can access any event of the organization even without being a member of the sub-teams
|
||||
@@ -284,7 +288,7 @@ export async function getRawEventType({
|
||||
});
|
||||
}
|
||||
|
||||
// Regular(Non Platform) Organization member(admin/non-admin) can access any event-type they are are a member of including sub-team events and Regular Team(non-subteam) events.
|
||||
// Regular(Non Platform) Organization member(admin/non-admin) can access any event-type they are are a member of including sub-team events and Regular Team(non-subteam) events.
|
||||
// Remember an organization member can stay a part of Regular Team still if that team hasn't been moved to the organization yet.
|
||||
return await eventTypeRepo.findById({
|
||||
id: eventTypeId,
|
||||
|
||||
@@ -20,5 +20,6 @@ export const getHandler = ({ ctx, input }: GetOptions) => {
|
||||
prisma: ctx.prisma,
|
||||
isTrpcCall: true,
|
||||
isUserOrganizationAdmin: !!ctx.user?.organization?.isOrgAdmin,
|
||||
userLocale: ctx.user.locale,
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user