mirror of
https://github.com/formbricks/formbricks.git
synced 2026-03-20 19:01:14 -05:00
fix: email locale in invite accepted email (#7124)
This commit is contained in:
committed by
GitHub
parent
047750967c
commit
2b526a87ca
@@ -58,7 +58,7 @@ async function handleEmailUpdate({
|
||||
payload.email = inputEmail;
|
||||
await updateBrevoCustomer({ id: ctx.user.id, email: inputEmail });
|
||||
} else {
|
||||
await sendVerificationNewEmail(ctx.user.id, inputEmail);
|
||||
await sendVerificationNewEmail(ctx.user.id, inputEmail, ctx.user.locale);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ export const sendEmbedSurveyPreviewEmailAction = authenticatedActionClient
|
||||
ctx.user.email,
|
||||
emailHtml,
|
||||
survey.environmentId,
|
||||
ctx.user.locale,
|
||||
organizationLogoUrl || ""
|
||||
);
|
||||
});
|
||||
|
||||
@@ -215,7 +215,14 @@ export const POST = async (request: Request) => {
|
||||
}
|
||||
|
||||
const emailPromises = usersWithNotifications.map((user) =>
|
||||
sendResponseFinishedEmail(user.email, environmentId, survey, response, responseCount).catch((error) => {
|
||||
sendResponseFinishedEmail(
|
||||
user.email,
|
||||
user.locale,
|
||||
environmentId,
|
||||
survey,
|
||||
response,
|
||||
responseCount
|
||||
).catch((error) => {
|
||||
logger.error(
|
||||
{ error, url: request.url, userEmail: user.email },
|
||||
`Failed to send email to ${user.email}`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { getLocale } from "@/lingodotdev/language";
|
||||
import { getTranslate } from "./server";
|
||||
|
||||
@@ -11,6 +11,10 @@ vi.mock("@/lingodotdev/shared", () => ({
|
||||
}));
|
||||
|
||||
describe("lingodotdev server", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("should get translate", async () => {
|
||||
vi.mocked(getLocale).mockResolvedValue("en-US");
|
||||
const translate = await getTranslate();
|
||||
@@ -22,4 +26,16 @@ describe("lingodotdev server", () => {
|
||||
const translate = await getTranslate();
|
||||
expect(translate).toBeDefined();
|
||||
});
|
||||
|
||||
test("should use provided locale instead of calling getLocale", async () => {
|
||||
const translate = await getTranslate("de-DE");
|
||||
expect(getLocale).not.toHaveBeenCalled();
|
||||
expect(translate).toBeDefined();
|
||||
});
|
||||
|
||||
test("should call getLocale when locale is not provided", async () => {
|
||||
vi.mocked(getLocale).mockResolvedValue("fr-FR");
|
||||
await getTranslate();
|
||||
expect(getLocale).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import ICU from "i18next-icu";
|
||||
import resourcesToBackend from "i18next-resources-to-backend";
|
||||
import { initReactI18next } from "react-i18next/initReactI18next";
|
||||
import { DEFAULT_LOCALE } from "@/lib/constants";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { getLocale } from "@/lingodotdev/language";
|
||||
|
||||
const initI18next = async (lng: string) => {
|
||||
@@ -21,9 +22,9 @@ const initI18next = async (lng: string) => {
|
||||
return i18nInstance;
|
||||
};
|
||||
|
||||
export async function getTranslate() {
|
||||
const locale = await getLocale();
|
||||
export async function getTranslate(locale?: TUserLocale) {
|
||||
const resolvedLocale = locale ?? (await getLocale());
|
||||
|
||||
const i18nextInstance = await initI18next(locale);
|
||||
return i18nextInstance.getFixedT(locale);
|
||||
const i18nextInstance = await initI18next(resolvedLocale);
|
||||
return i18nextInstance.getFixedT(resolvedLocale);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export const resetPasswordAction = actionClient.schema(ZResetPasswordAction).act
|
||||
ctx.auditLoggingCtx.oldObject = oldObject;
|
||||
ctx.auditLoggingCtx.newObject = updatedUser;
|
||||
|
||||
await sendPasswordResetNotifyEmail(updatedUser);
|
||||
await sendPasswordResetNotifyEmail({ email: updatedUser.email, locale: updatedUser.locale });
|
||||
return { success: true };
|
||||
}
|
||||
)
|
||||
|
||||
@@ -69,6 +69,7 @@ describe("invite", () => {
|
||||
creator: {
|
||||
name: "Test User",
|
||||
email: "test@example.com",
|
||||
locale: "en-US",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -89,6 +90,7 @@ describe("invite", () => {
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
locale: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -46,6 +46,7 @@ export const getInvite = reactCache(async (inviteId: string): Promise<InviteWith
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
locale: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -102,7 +102,12 @@ export const InvitePage = async (props: InvitePageProps) => {
|
||||
);
|
||||
}
|
||||
await deleteInvite(inviteId);
|
||||
await sendInviteAcceptedEmail(invite.creator.name ?? "", user?.name ?? "", invite.creator.email);
|
||||
await sendInviteAcceptedEmail(
|
||||
invite.creator.name ?? "",
|
||||
user?.name ?? "",
|
||||
invite.creator.email,
|
||||
invite.creator.locale
|
||||
);
|
||||
await updateUser(session.user.id, {
|
||||
notificationSettings: {
|
||||
...user.notificationSettings,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Invite } from "@prisma/client";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
|
||||
export interface InviteWithCreator
|
||||
extends Pick<Invite, "id" | "expiresAt" | "organizationId" | "role" | "teamIds"> {
|
||||
creator: {
|
||||
name: string | null;
|
||||
email: string;
|
||||
locale: TUserLocale;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,12 @@ async function handleInviteAcceptance(
|
||||
},
|
||||
});
|
||||
|
||||
await sendInviteAcceptedEmail(invite.creator.name ?? "", user.name, invite.creator.email);
|
||||
await sendInviteAcceptedEmail(
|
||||
invite.creator.name ?? "",
|
||||
user.name,
|
||||
invite.creator.email,
|
||||
invite.creator.locale
|
||||
);
|
||||
await deleteInvite(invite.id);
|
||||
}
|
||||
|
||||
@@ -168,7 +173,7 @@ async function handlePostUserCreation(
|
||||
}
|
||||
|
||||
if (!emailVerificationDisabled) {
|
||||
await sendVerificationEmail(user);
|
||||
await sendVerificationEmail({ id: user.id, email: user.email, locale: user.locale });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,7 @@ describe("resendVerificationEmailAction", () => {
|
||||
const mockUser = {
|
||||
id: "user123",
|
||||
email: "test@example.com",
|
||||
emailVerified: null, // Not verified
|
||||
name: "Test User",
|
||||
locale: "en-US",
|
||||
};
|
||||
|
||||
const mockVerifiedUser = {
|
||||
|
||||
@@ -32,7 +32,7 @@ export const resendVerificationEmailAction = actionClient.schema(ZResendVerifica
|
||||
};
|
||||
}
|
||||
ctx.auditLoggingCtx.userId = user.id;
|
||||
await sendVerificationEmail(user);
|
||||
await sendVerificationEmail({ id: user.id, email: user.email, locale: user.locale });
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
|
||||
@@ -121,6 +121,7 @@ export const sendTestEmailAction = authenticatedActionClient
|
||||
await sendEmailCustomizationPreviewEmail(
|
||||
ctx.user.email,
|
||||
ctx.user.name,
|
||||
ctx.user.locale,
|
||||
organization?.whitelabel?.logoUrl || ""
|
||||
);
|
||||
|
||||
|
||||
@@ -71,12 +71,12 @@ export const sendEmail = async (emailData: SendEmailDataProps): Promise<boolean>
|
||||
secure: SMTP_SECURE_ENABLED, // true for 465, false for other ports
|
||||
...(SMTP_AUTHENTICATED
|
||||
? {
|
||||
auth: {
|
||||
type: "LOGIN",
|
||||
user: SMTP_USER,
|
||||
pass: SMTP_PASSWORD,
|
||||
},
|
||||
}
|
||||
auth: {
|
||||
type: "LOGIN",
|
||||
user: SMTP_USER,
|
||||
pass: SMTP_PASSWORD,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
tls: {
|
||||
rejectUnauthorized: SMTP_REJECT_UNAUTHORIZED_TLS,
|
||||
@@ -97,9 +97,13 @@ export const sendEmail = async (emailData: SendEmailDataProps): Promise<boolean>
|
||||
}
|
||||
};
|
||||
|
||||
export const sendVerificationNewEmail = async (id: string, email: string): Promise<boolean> => {
|
||||
export const sendVerificationNewEmail = async (
|
||||
id: string,
|
||||
email: string,
|
||||
locale: TUserLocale
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(locale);
|
||||
const token = createEmailChangeToken(id, email);
|
||||
const verifyLink = `${WEBAPP_URL}/verify-email-change?token=${encodeURIComponent(token)}`;
|
||||
|
||||
@@ -119,12 +123,14 @@ export const sendVerificationNewEmail = async (id: string, email: string): Promi
|
||||
export const sendVerificationEmail = async ({
|
||||
id,
|
||||
email,
|
||||
locale,
|
||||
}: {
|
||||
id: string;
|
||||
email: TUserEmail;
|
||||
locale: TUserLocale;
|
||||
}): Promise<boolean> => {
|
||||
try {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(locale);
|
||||
const token = createToken(id, {
|
||||
expiresIn: "1d",
|
||||
});
|
||||
@@ -154,7 +160,7 @@ export const sendForgotPasswordEmail = async (user: {
|
||||
email: TUserEmail;
|
||||
locale: TUserLocale;
|
||||
}): Promise<boolean> => {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(user.locale);
|
||||
const token = createToken(user.id, {
|
||||
expiresIn: "1d",
|
||||
});
|
||||
@@ -167,8 +173,11 @@ export const sendForgotPasswordEmail = async (user: {
|
||||
});
|
||||
};
|
||||
|
||||
export const sendPasswordResetNotifyEmail = async (user: { email: string }): Promise<boolean> => {
|
||||
const t = await getTranslate();
|
||||
export const sendPasswordResetNotifyEmail = async (user: {
|
||||
email: string;
|
||||
locale: TUserLocale;
|
||||
}): Promise<boolean> => {
|
||||
const t = await getTranslate(user.locale);
|
||||
const html = await renderPasswordResetNotifyEmail({ t, ...legalProps });
|
||||
return await sendEmail({
|
||||
to: user.email,
|
||||
@@ -201,9 +210,10 @@ export const sendInviteMemberEmail = async (
|
||||
export const sendInviteAcceptedEmail = async (
|
||||
inviterName: string,
|
||||
inviteeName: string,
|
||||
email: string
|
||||
email: string,
|
||||
inviterLocale?: TUserLocale
|
||||
): Promise<void> => {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(inviterLocale);
|
||||
const html = await renderInviteAcceptedEmail({ inviteeName, inviterName, t, ...legalProps });
|
||||
await sendEmail({
|
||||
to: email,
|
||||
@@ -214,12 +224,13 @@ export const sendInviteAcceptedEmail = async (
|
||||
|
||||
export const sendResponseFinishedEmail = async (
|
||||
email: string,
|
||||
locale: TUserLocale,
|
||||
environmentId: string,
|
||||
survey: TSurvey,
|
||||
response: TResponse,
|
||||
responseCount: number
|
||||
): Promise<void> => {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(locale);
|
||||
const personEmail = response.contactAttributes?.email;
|
||||
const organization = await getOrganizationByEnvironmentId(environmentId);
|
||||
|
||||
@@ -246,12 +257,12 @@ export const sendResponseFinishedEmail = async (
|
||||
to: email,
|
||||
subject: personEmail
|
||||
? t("emails.response_finished_email_subject_with_email", {
|
||||
personEmail,
|
||||
surveyName: survey.name,
|
||||
})
|
||||
personEmail,
|
||||
surveyName: survey.name,
|
||||
})
|
||||
: t("emails.response_finished_email_subject", {
|
||||
surveyName: survey.name,
|
||||
}),
|
||||
surveyName: survey.name,
|
||||
}),
|
||||
replyTo: personEmail?.toString() ?? MAIL_FROM,
|
||||
html,
|
||||
});
|
||||
@@ -261,9 +272,10 @@ export const sendEmbedSurveyPreviewEmail = async (
|
||||
to: string,
|
||||
innerHtml: string,
|
||||
environmentId: string,
|
||||
locale: TUserLocale,
|
||||
logoUrl?: string
|
||||
): Promise<boolean> => {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(locale);
|
||||
const html = await renderEmbedSurveyPreviewEmail({
|
||||
html: innerHtml,
|
||||
environmentId,
|
||||
@@ -281,9 +293,10 @@ export const sendEmbedSurveyPreviewEmail = async (
|
||||
export const sendEmailCustomizationPreviewEmail = async (
|
||||
to: string,
|
||||
userName: string,
|
||||
locale: TUserLocale,
|
||||
logoUrl?: string
|
||||
): Promise<boolean> => {
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(locale);
|
||||
const emailHtmlBody = await renderEmailCustomizationPreviewEmail({
|
||||
userName,
|
||||
logoUrl,
|
||||
@@ -305,7 +318,7 @@ export const sendLinkSurveyToVerifiedEmail = async (data: TLinkSurveyEmailData):
|
||||
const singleUseId = data.suId;
|
||||
const logoUrl = data.logoUrl || "";
|
||||
const token = createTokenForLinkSurvey(surveyId, email);
|
||||
const t = await getTranslate();
|
||||
const t = await getTranslate(data.locale);
|
||||
const getSurveyLink = (): string => {
|
||||
if (singleUseId) {
|
||||
return `${getPublicDomain()}/s/${surveyId}?verify=${encodeURIComponent(token)}&suId=${singleUseId}`;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ArrowLeft, MailIcon } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { Toaster, toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -10,11 +10,13 @@ import { z } from "zod";
|
||||
import { TProjectStyling } from "@formbricks/types/project";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { getTextContent } from "@formbricks/types/surveys/validation";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { getLocalizedValue } from "@/lib/i18n/utils";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { replaceHeadlineRecall } from "@/lib/utils/recall";
|
||||
import { getElementsFromBlocks } from "@/modules/survey/lib/client-utils";
|
||||
import { isSurveyResponsePresentAction, sendLinkSurveyEmailAction } from "@/modules/survey/link/actions";
|
||||
import { getWebAppLocale } from "@/modules/survey/link/lib/utils";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { FormControl, FormError, FormField, FormItem } from "@/modules/ui/components/form";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
@@ -26,7 +28,7 @@ interface VerifyEmailProps {
|
||||
singleUseId?: string;
|
||||
languageCode: string;
|
||||
styling: TProjectStyling;
|
||||
locale: string;
|
||||
locale: TUserLocale;
|
||||
}
|
||||
|
||||
const ZVerifyEmailInput = z.object({
|
||||
@@ -42,7 +44,18 @@ export const VerifyEmail = ({
|
||||
styling,
|
||||
locale,
|
||||
}: VerifyEmailProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
// Set i18n language based on survey language
|
||||
useEffect(() => {
|
||||
const webAppLocale = getWebAppLocale(languageCode, survey);
|
||||
if (i18n.language !== webAppLocale) {
|
||||
i18n.changeLanguage(webAppLocale).catch(() => {
|
||||
// If changeLanguage fails, fallback to default locale
|
||||
i18n.changeLanguage("en-US");
|
||||
});
|
||||
}
|
||||
}, [languageCode, survey, i18n]);
|
||||
const form = useForm<TVerifyEmailInput>({
|
||||
defaultValues: {
|
||||
email: "",
|
||||
@@ -175,7 +188,7 @@ export const VerifyEmail = ({
|
||||
{!emailSent && showPreviewQuestions && (
|
||||
<div>
|
||||
<p className="text-2xl font-bold">{t("s.question_preview")}</p>
|
||||
<div className="mt-4 flex max-h-[50vh] w-full flex-col overflow-y-auto rounded-lg border border-slate-200 bg-slate-50 bg-opacity-20 p-4 text-slate-700">
|
||||
<div className="bg-opacity-20 mt-4 flex max-h-[50vh] w-full flex-col overflow-y-auto rounded-lg border border-slate-200 bg-slate-50 p-4 text-slate-700">
|
||||
{questions.map((question, index) => (
|
||||
<p
|
||||
key={index}
|
||||
|
||||
85
apps/web/modules/survey/link/lib/utils.test.ts
Normal file
85
apps/web/modules/survey/link/lib/utils.test.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { getWebAppLocale } from "./utils";
|
||||
|
||||
describe("getWebAppLocale", () => {
|
||||
const createMockSurvey = (languages: TSurvey["languages"] = []): TSurvey => {
|
||||
return {
|
||||
id: "survey-1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
name: "Test Survey",
|
||||
type: "link",
|
||||
environmentId: "env-1",
|
||||
createdBy: null,
|
||||
status: "draft",
|
||||
displayOption: "displayOnce",
|
||||
autoClose: null,
|
||||
triggers: [],
|
||||
recontactDays: null,
|
||||
displayLimit: null,
|
||||
welcomeCard: {
|
||||
enabled: false,
|
||||
headline: { default: "Welcome" },
|
||||
timeToFinish: false,
|
||||
showResponseCount: false,
|
||||
},
|
||||
questions: [],
|
||||
blocks: [],
|
||||
endings: [],
|
||||
hiddenFields: { enabled: false, fieldIds: [] },
|
||||
variables: [],
|
||||
styling: null,
|
||||
segment: null,
|
||||
languages,
|
||||
displayPercentage: null,
|
||||
isVerifyEmailEnabled: false,
|
||||
isSingleResponsePerEmailEnabled: false,
|
||||
singleUse: null,
|
||||
pin: null,
|
||||
projectOverwrites: null,
|
||||
surveyClosedMessage: null,
|
||||
followUps: [],
|
||||
delay: 0,
|
||||
autoComplete: null,
|
||||
showLanguageSwitch: null,
|
||||
recaptcha: null,
|
||||
isBackButtonHidden: false,
|
||||
isCaptureIpEnabled: false,
|
||||
slug: null,
|
||||
metadata: {},
|
||||
} as TSurvey;
|
||||
};
|
||||
|
||||
test("maps language codes to web app locales", () => {
|
||||
const survey = createMockSurvey();
|
||||
expect(getWebAppLocale("en", survey)).toBe("en-US");
|
||||
expect(getWebAppLocale("de", survey)).toBe("de-DE");
|
||||
expect(getWebAppLocale("pt-BR", survey)).toBe("pt-BR");
|
||||
});
|
||||
|
||||
test("handles 'default' languageCode by finding default language in survey", () => {
|
||||
const survey = createMockSurvey([
|
||||
{
|
||||
language: {
|
||||
id: "lang1",
|
||||
code: "de",
|
||||
alias: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
projectId: "proj1",
|
||||
},
|
||||
default: true,
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(getWebAppLocale("default", survey)).toBe("de-DE");
|
||||
});
|
||||
|
||||
test("falls back to en-US when language is not supported", () => {
|
||||
const survey = createMockSurvey();
|
||||
expect(getWebAppLocale("default", survey)).toBe("en-US");
|
||||
expect(getWebAppLocale("xx", survey)).toBe("en-US");
|
||||
});
|
||||
});
|
||||
@@ -1,2 +1,54 @@
|
||||
// Prefilling logic has been moved to @/modules/survey/link/lib/prefill
|
||||
// This file is kept for any future utility functions
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
|
||||
/**
|
||||
* Maps survey language codes to web app locale codes.
|
||||
* Falls back to "en-US" if the language is not available in web app locales.
|
||||
*/
|
||||
export const getWebAppLocale = (languageCode: string, survey: TSurvey): string => {
|
||||
// Map of common 2-letter language codes to web app locale codes
|
||||
const languageToLocaleMap: Record<string, string> = {
|
||||
en: "en-US",
|
||||
de: "de-DE",
|
||||
pt: "pt-BR", // Default to Brazilian Portuguese
|
||||
"pt-BR": "pt-BR",
|
||||
"pt-PT": "pt-PT",
|
||||
fr: "fr-FR",
|
||||
nl: "nl-NL",
|
||||
zh: "zh-Hans-CN", // Default to Simplified Chinese
|
||||
"zh-Hans": "zh-Hans-CN",
|
||||
"zh-Hans-CN": "zh-Hans-CN",
|
||||
"zh-Hant": "zh-Hant-TW",
|
||||
"zh-Hant-TW": "zh-Hant-TW",
|
||||
ro: "ro-RO",
|
||||
ja: "ja-JP",
|
||||
es: "es-ES",
|
||||
sv: "sv-SE",
|
||||
ru: "ru-RU",
|
||||
};
|
||||
|
||||
let codeToMap = languageCode;
|
||||
|
||||
// If languageCode is "default", get the default language from survey
|
||||
if (languageCode === "default") {
|
||||
const defaultLanguage = survey.languages?.find((lang) => lang.default);
|
||||
if (defaultLanguage) {
|
||||
codeToMap = defaultLanguage.language.code;
|
||||
} else {
|
||||
return "en-US";
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's already a web app locale code
|
||||
if (languageToLocaleMap[codeToMap]) {
|
||||
return languageToLocaleMap[codeToMap];
|
||||
}
|
||||
|
||||
// Try to find a match by base language code (e.g., "pt-BR" -> "pt")
|
||||
const baseCode = codeToMap.split("-")[0].toLowerCase();
|
||||
if (languageToLocaleMap[baseCode]) {
|
||||
return languageToLocaleMap[baseCode];
|
||||
}
|
||||
|
||||
// Fallback to English if language is not supported
|
||||
return "en-US";
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { z } from "zod";
|
||||
import { ZUserLocale } from "./user";
|
||||
|
||||
export const ZLinkSurveyEmailData = z.object({
|
||||
surveyId: z.string(),
|
||||
email: z.string(),
|
||||
suId: z.string().optional(),
|
||||
surveyName: z.string(),
|
||||
locale: z.string(),
|
||||
locale: ZUserLocale,
|
||||
logoUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user