mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 18:30:32 -06:00
fix: translate survey and migration script (#2290)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
33f5da8c94
commit
7b2cf9f3d8
@@ -241,7 +241,6 @@ export const ZSurveyConsentQuestion = ZSurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionType.Consent),
|
||||
html: z.string().optional(),
|
||||
label: z.string(),
|
||||
dismissButtonLabel: z.string().optional(),
|
||||
placeholder: z.string().optional(),
|
||||
logic: z.array(ZSurveyConsentLogic).optional(),
|
||||
});
|
||||
|
||||
@@ -70,10 +70,10 @@ export default async function WidgetStatusIndicator({ environmentId, type }: Wid
|
||||
<div
|
||||
className={clsx(
|
||||
"h-5 w-5 rounded-full",
|
||||
status === "notImplemented" && "border border-white bg-white text-amber-600",
|
||||
status === "notImplemented" && "text-amber-600",
|
||||
status === "running" && "bg-green-100 text-green-700"
|
||||
)}>
|
||||
<currentStatus.icon />
|
||||
<currentStatus.icon className="h-5 w-5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -237,7 +237,7 @@ export default function MultipleChoiceMultiForm({
|
||||
{question.choices &&
|
||||
question.choices.map((choice, choiceIdx) => (
|
||||
<div key={choiceIdx} className="inline-flex w-full items-center">
|
||||
<div className="w-full space-x-2">
|
||||
<div className="flex w-full space-x-2">
|
||||
<QuestionFormInput
|
||||
key={choice.id}
|
||||
id={`choice-${choiceIdx}`}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { isLabelValidForAllLanguages } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/components/Validation";
|
||||
import { isLabelValidForAllLanguages } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/lib/validation";
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { PlusIcon, TrashIcon } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
@@ -12,12 +12,12 @@ import { checkForEmptyFallBackValue, extractRecallInfo } from "@formbricks/lib/u
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { TSurvey, TSurveyQuestion } from "@formbricks/types/surveys";
|
||||
|
||||
import { isCardValid, validateQuestion, validateSurveyQuestionsInBatch } from "../lib/validation";
|
||||
import AddQuestionButton from "./AddQuestionButton";
|
||||
import EditThankYouCard from "./EditThankYouCard";
|
||||
import EditWelcomeCard from "./EditWelcomeCard";
|
||||
import QuestionCard from "./QuestionCard";
|
||||
import { StrictModeDroppable } from "./StrictModeDroppable";
|
||||
import { isCardValid, validateQuestion, validateSurveyQuestionsInBatch } from "./Validation";
|
||||
|
||||
interface QuestionsViewProps {
|
||||
localSurvey: TSurvey;
|
||||
|
||||
@@ -23,7 +23,7 @@ import { Input } from "@formbricks/ui/Input";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/Tooltip";
|
||||
|
||||
import { updateSurveyAction } from "../actions";
|
||||
import { isCardValid, validateQuestion } from "./Validation";
|
||||
import { isCardValid, validateQuestion } from "../lib/validation";
|
||||
|
||||
interface SurveyMenuBarProps {
|
||||
localSurvey: TSurvey;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// extend this object in order to add more validation rules
|
||||
import { extractLanguageCodes } from "@formbricks/lib/i18n/utils";
|
||||
import { extractLanguageCodes, getLocalizedValue } from "@formbricks/lib/i18n/utils";
|
||||
import {
|
||||
TI18nString,
|
||||
TSurveyCTAQuestion,
|
||||
TSurveyConsentQuestion,
|
||||
TSurveyLanguage,
|
||||
TSurveyMultipleChoiceMultiQuestion,
|
||||
TSurveyMultipleChoiceSingleQuestion,
|
||||
TSurveyOpenTextQuestion,
|
||||
TSurveyPictureSelectionQuestion,
|
||||
TSurveyQuestion,
|
||||
TSurveyThankYouCard,
|
||||
@@ -32,6 +34,13 @@ const handleI18nCheckForMultipleChoice = (
|
||||
|
||||
// Validation rules
|
||||
const validationRules = {
|
||||
openText: (question: TSurveyOpenTextQuestion, languages: TSurveyLanguage[]) => {
|
||||
return question.placeholder &&
|
||||
getLocalizedValue(question.placeholder, "default").trim() !== "" &&
|
||||
languages.length > 1
|
||||
? isLabelValidForAllLanguages(question.placeholder, languages)
|
||||
: true;
|
||||
},
|
||||
multipleChoiceMulti: (question: TSurveyMultipleChoiceMultiQuestion, languages: TSurveyLanguage[]) => {
|
||||
return handleI18nCheckForMultipleChoice(question, languages);
|
||||
},
|
||||
@@ -44,14 +53,16 @@ const validationRules = {
|
||||
pictureSelection: (question: TSurveyPictureSelectionQuestion) => {
|
||||
return question.choices.length >= 2;
|
||||
},
|
||||
cta: (question: TSurveyCTAQuestion, languages: TSurveyLanguage[]) => {
|
||||
return !question.required && question.dismissButtonLabel
|
||||
? isLabelValidForAllLanguages(question.dismissButtonLabel, languages)
|
||||
: true;
|
||||
},
|
||||
// Assuming headline is of type TI18nString
|
||||
defaultValidation: (question: TSurveyQuestion, languages: TSurveyLanguage[]) => {
|
||||
let isValid = isLabelValidForAllLanguages(question.headline, languages);
|
||||
let isValidCTADismissLabel = true;
|
||||
const isHeadlineValid = isLabelValidForAllLanguages(question.headline, languages);
|
||||
let isValid = isHeadlineValid;
|
||||
const defaultLanguageCode = "default";
|
||||
if (question.type === "cta" && !question.required && question.dismissButtonLabel) {
|
||||
isValidCTADismissLabel = isLabelValidForAllLanguages(question.dismissButtonLabel, languages);
|
||||
}
|
||||
const fieldsToValidate = [
|
||||
"subheader",
|
||||
"html",
|
||||
@@ -59,13 +70,11 @@ const validationRules = {
|
||||
"upperLabel",
|
||||
"backButtonLabel",
|
||||
"lowerLabel",
|
||||
"placeholder",
|
||||
];
|
||||
|
||||
for (const field of fieldsToValidate) {
|
||||
if (question[field] && question[field][defaultLanguageCode]) {
|
||||
isValid =
|
||||
isValid && isLabelValidForAllLanguages(question[field], languages) && isValidCTADismissLabel;
|
||||
if (question[field] && typeof question[field][defaultLanguageCode] !== "undefined") {
|
||||
isValid = isValid && isLabelValidForAllLanguages(question[field], languages);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +326,6 @@ export const testTemplate: TTemplate = {
|
||||
headline: { default: "This is a Consent question" },
|
||||
required: true,
|
||||
label: { default: "I agree to the terms and conditions" },
|
||||
dismissButtonLabel: "Skip",
|
||||
},
|
||||
{
|
||||
id: createId(),
|
||||
@@ -334,7 +333,6 @@ export const testTemplate: TTemplate = {
|
||||
headline: { default: "This is a Consent question" },
|
||||
required: false,
|
||||
label: { default: "I agree to the terms and conditions" },
|
||||
dismissButtonLabel: "Skip",
|
||||
},
|
||||
],
|
||||
thankYouCard: thankYouCardDefault,
|
||||
|
||||
@@ -137,7 +137,6 @@ export const questionTypes: TSurveyQuestionType[] = [
|
||||
headline: { default: "Terms and Conditions" },
|
||||
html: { default: "" },
|
||||
label: { default: "I agree to the terms and conditions" },
|
||||
dismissButtonLabel: "Skip",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// migration script to translate surveys where thankYouCard buttonLabel is a string or question subheaders are strings
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
import { hasStringSubheaders, translateSurvey } from "./lib/i18n";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
await prisma.$transaction(
|
||||
async (tx) => {
|
||||
// Translate Surveys
|
||||
const surveys = await tx.survey.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
questions: true,
|
||||
thankYouCard: true,
|
||||
welcomeCard: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!surveys) {
|
||||
// stop the migration if there are no surveys
|
||||
return;
|
||||
}
|
||||
|
||||
for (const survey of surveys) {
|
||||
if (typeof survey.thankYouCard.buttonLabel === "string" || hasStringSubheaders(survey.questions)) {
|
||||
const translatedSurvey = translateSurvey(survey, []);
|
||||
|
||||
// Save the translated survey
|
||||
await tx.survey.update({
|
||||
where: { id: survey.id },
|
||||
data: { ...translatedSurvey },
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
timeout: 50000,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => await prisma.$disconnect());
|
||||
@@ -1,13 +1,35 @@
|
||||
import {
|
||||
TLegacySurveyChoice,
|
||||
TLegacySurveyQuestion,
|
||||
TLegacySurveyThankYouCard,
|
||||
TLegacySurveyWelcomeCard,
|
||||
} from "@formbricks/types/LegacySurvey";
|
||||
import { TLanguage } from "@formbricks/types/product";
|
||||
import {
|
||||
TI18nString,
|
||||
TSurveyCTAQuestion,
|
||||
TSurveyChoice,
|
||||
TSurveyConsentQuestion,
|
||||
TSurveyMultipleChoiceSingleQuestion,
|
||||
TSurveyNPSQuestion,
|
||||
TSurveyOpenTextQuestion,
|
||||
TSurveyQuestions,
|
||||
TSurveyRatingQuestion,
|
||||
TSurveyThankYouCard,
|
||||
TSurveyWelcomeCard,
|
||||
ZSurveyCTAQuestion,
|
||||
ZSurveyCalQuestion,
|
||||
ZSurveyConsentQuestion,
|
||||
ZSurveyFileUploadQuestion,
|
||||
ZSurveyMultipleChoiceMultiQuestion,
|
||||
ZSurveyMultipleChoiceSingleQuestion,
|
||||
ZSurveyNPSQuestion,
|
||||
ZSurveyOpenTextQuestion,
|
||||
ZSurveyPictureSelectionQuestion,
|
||||
ZSurveyQuestion,
|
||||
ZSurveyRatingQuestion,
|
||||
ZSurveyThankYouCard,
|
||||
ZSurveyWelcomeCard,
|
||||
} from "@formbricks/types/surveys";
|
||||
import { TSurvey, TSurveyMultipleChoiceMultiQuestion, TSurveyQuestion } from "@formbricks/types/surveys";
|
||||
|
||||
@@ -49,120 +71,177 @@ export const createI18nString = (text: string | TI18nString, languages: string[]
|
||||
};
|
||||
|
||||
// Function to translate a choice label
|
||||
const translateChoice = (choice: any, languages: string[]) => {
|
||||
// Assuming choice is a simple object and choice.label is a string.
|
||||
return {
|
||||
...choice,
|
||||
label: createI18nString(choice.label, languages),
|
||||
};
|
||||
const translateChoice = (choice: TSurveyChoice | TLegacySurveyChoice, languages: string[]): TSurveyChoice => {
|
||||
if (typeof choice.label !== "undefined") {
|
||||
return {
|
||||
...choice,
|
||||
label: createI18nString(choice.label, languages),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...choice,
|
||||
label: choice.label,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const translateWelcomeCard = (
|
||||
welcomeCard: TSurveyWelcomeCard,
|
||||
welcomeCard: TSurveyWelcomeCard | TLegacySurveyWelcomeCard,
|
||||
languages: string[]
|
||||
): TSurveyWelcomeCard => {
|
||||
const clonedWelcomeCard = structuredClone(welcomeCard);
|
||||
clonedWelcomeCard.headline = createI18nString(welcomeCard.headline, languages);
|
||||
clonedWelcomeCard.html = createI18nString(welcomeCard.html ?? "", languages);
|
||||
if (clonedWelcomeCard.buttonLabel) {
|
||||
clonedWelcomeCard.buttonLabel = createI18nString(clonedWelcomeCard.buttonLabel, languages);
|
||||
if (typeof welcomeCard.headline !== "undefined") {
|
||||
clonedWelcomeCard.headline = createI18nString(welcomeCard.headline ?? "", languages);
|
||||
}
|
||||
if (typeof welcomeCard.html !== "undefined") {
|
||||
clonedWelcomeCard.html = createI18nString(welcomeCard.html ?? "", languages);
|
||||
}
|
||||
if (typeof welcomeCard.buttonLabel !== "undefined") {
|
||||
clonedWelcomeCard.buttonLabel = createI18nString(clonedWelcomeCard.buttonLabel ?? "", languages);
|
||||
}
|
||||
|
||||
return clonedWelcomeCard;
|
||||
return ZSurveyWelcomeCard.parse(clonedWelcomeCard);
|
||||
};
|
||||
|
||||
const translateThankYouCard = (
|
||||
thankYouCard: TSurveyThankYouCard,
|
||||
thankYouCard: TSurveyThankYouCard | TLegacySurveyThankYouCard,
|
||||
languages: string[]
|
||||
): TSurveyThankYouCard => {
|
||||
const clonedThankYouCard = structuredClone(thankYouCard);
|
||||
clonedThankYouCard.headline = createI18nString(
|
||||
thankYouCard.headline ? thankYouCard.headline : "",
|
||||
languages
|
||||
);
|
||||
if (clonedThankYouCard.subheader) {
|
||||
clonedThankYouCard.subheader = createI18nString(
|
||||
thankYouCard.subheader ? thankYouCard.subheader : "",
|
||||
languages
|
||||
);
|
||||
|
||||
if (typeof thankYouCard.headline !== "undefined") {
|
||||
clonedThankYouCard.headline = createI18nString(thankYouCard.headline ?? "", languages);
|
||||
}
|
||||
|
||||
return clonedThankYouCard;
|
||||
if (typeof thankYouCard.subheader !== "undefined") {
|
||||
clonedThankYouCard.subheader = createI18nString(thankYouCard.subheader ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof clonedThankYouCard.buttonLabel !== "undefined") {
|
||||
clonedThankYouCard.buttonLabel = createI18nString(thankYouCard.buttonLabel ?? "", languages);
|
||||
}
|
||||
return ZSurveyThankYouCard.parse(clonedThankYouCard);
|
||||
};
|
||||
|
||||
// Function that will translate a single question
|
||||
const translateQuestion = (question: TSurveyQuestion, languages: string[]) => {
|
||||
const translateQuestion = (
|
||||
question: TLegacySurveyQuestion | TSurveyQuestion,
|
||||
languages: string[]
|
||||
): TSurveyQuestion => {
|
||||
// Clone the question to avoid mutating the original
|
||||
const clonedQuestion = structuredClone(question);
|
||||
|
||||
clonedQuestion.headline = createI18nString(question.headline, languages);
|
||||
if (clonedQuestion.subheader) {
|
||||
//common question properties
|
||||
if (typeof question.headline !== "undefined") {
|
||||
clonedQuestion.headline = createI18nString(question.headline ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof question.subheader !== "undefined") {
|
||||
clonedQuestion.subheader = createI18nString(question.subheader ?? "", languages);
|
||||
}
|
||||
|
||||
if (clonedQuestion.buttonLabel) {
|
||||
if (typeof question.buttonLabel !== "undefined") {
|
||||
clonedQuestion.buttonLabel = createI18nString(question.buttonLabel ?? "", languages);
|
||||
}
|
||||
|
||||
if (clonedQuestion.backButtonLabel) {
|
||||
if (typeof question.backButtonLabel !== "undefined") {
|
||||
clonedQuestion.backButtonLabel = createI18nString(question.backButtonLabel ?? "", languages);
|
||||
}
|
||||
|
||||
if (question.type === "multipleChoiceSingle" || question.type === "multipleChoiceMulti") {
|
||||
(clonedQuestion as TSurveyMultipleChoiceMultiQuestion | TSurveyMultipleChoiceMultiQuestion).choices =
|
||||
question.choices.map((choice) => translateChoice(structuredClone(choice), languages));
|
||||
(
|
||||
clonedQuestion as TSurveyMultipleChoiceMultiQuestion | TSurveyMultipleChoiceMultiQuestion
|
||||
).otherOptionPlaceholder = question.otherOptionPlaceholder
|
||||
? createI18nString(question.otherOptionPlaceholder, languages)
|
||||
: undefined;
|
||||
}
|
||||
if (question.type === "openText") {
|
||||
(clonedQuestion as TSurveyOpenTextQuestion).placeholder = createI18nString(
|
||||
question.placeholder ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
if (question.type === "cta") {
|
||||
if (question.dismissButtonLabel) {
|
||||
(clonedQuestion as TSurveyCTAQuestion).dismissButtonLabel = createI18nString(
|
||||
question.dismissButtonLabel,
|
||||
languages
|
||||
);
|
||||
}
|
||||
if (question.html) {
|
||||
(clonedQuestion as TSurveyCTAQuestion).html = createI18nString(question.html, languages);
|
||||
}
|
||||
}
|
||||
if (question.type === "consent") {
|
||||
if (question.html) {
|
||||
(clonedQuestion as TSurveyConsentQuestion).html = createI18nString(question.html, languages);
|
||||
}
|
||||
switch (question.type) {
|
||||
case "openText":
|
||||
if (typeof question.placeholder !== "undefined") {
|
||||
(clonedQuestion as TSurveyOpenTextQuestion).placeholder = createI18nString(
|
||||
question.placeholder ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
return ZSurveyOpenTextQuestion.parse(clonedQuestion);
|
||||
|
||||
if (question.label) {
|
||||
(clonedQuestion as TSurveyConsentQuestion).label = createI18nString(question.label, languages);
|
||||
}
|
||||
case "multipleChoiceSingle":
|
||||
case "multipleChoiceMulti":
|
||||
(clonedQuestion as TSurveyMultipleChoiceSingleQuestion | TSurveyMultipleChoiceMultiQuestion).choices =
|
||||
question.choices.map((choice) => {
|
||||
return translateChoice(choice, languages);
|
||||
});
|
||||
if (
|
||||
typeof (clonedQuestion as TSurveyMultipleChoiceSingleQuestion | TSurveyMultipleChoiceMultiQuestion)
|
||||
.otherOptionPlaceholder !== "undefined"
|
||||
) {
|
||||
(
|
||||
clonedQuestion as TSurveyMultipleChoiceSingleQuestion | TSurveyMultipleChoiceMultiQuestion
|
||||
).otherOptionPlaceholder = createI18nString(question.otherOptionPlaceholder ?? "", languages);
|
||||
}
|
||||
if (question.type === "multipleChoiceSingle") {
|
||||
return ZSurveyMultipleChoiceSingleQuestion.parse(clonedQuestion);
|
||||
} else return ZSurveyMultipleChoiceMultiQuestion.parse(clonedQuestion);
|
||||
|
||||
case "cta":
|
||||
if (typeof question.dismissButtonLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyCTAQuestion).dismissButtonLabel = createI18nString(
|
||||
question.dismissButtonLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
if (typeof question.html !== "undefined") {
|
||||
(clonedQuestion as TSurveyCTAQuestion).html = createI18nString(question.html ?? "", languages);
|
||||
}
|
||||
return ZSurveyCTAQuestion.parse(clonedQuestion);
|
||||
|
||||
case "consent":
|
||||
if (typeof question.html !== "undefined") {
|
||||
(clonedQuestion as TSurveyConsentQuestion).html = createI18nString(question.html ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof question.label !== "undefined") {
|
||||
(clonedQuestion as TSurveyConsentQuestion).label = createI18nString(question.label ?? "", languages);
|
||||
}
|
||||
|
||||
return ZSurveyConsentQuestion.parse(clonedQuestion);
|
||||
|
||||
case "nps":
|
||||
if (typeof question.lowerLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyNPSQuestion).lowerLabel = createI18nString(
|
||||
question.lowerLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
if (typeof question.upperLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyNPSQuestion).upperLabel = createI18nString(
|
||||
question.upperLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
return ZSurveyNPSQuestion.parse(clonedQuestion);
|
||||
|
||||
case "rating":
|
||||
if (typeof question.lowerLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyRatingQuestion).lowerLabel = createI18nString(
|
||||
question.lowerLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof question.upperLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyRatingQuestion).upperLabel = createI18nString(
|
||||
question.upperLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
return ZSurveyRatingQuestion.parse(clonedQuestion);
|
||||
|
||||
case "fileUpload":
|
||||
return ZSurveyFileUploadQuestion.parse(clonedQuestion);
|
||||
|
||||
case "pictureSelection":
|
||||
return ZSurveyPictureSelectionQuestion.parse(clonedQuestion);
|
||||
|
||||
case "cal":
|
||||
return ZSurveyCalQuestion.parse(clonedQuestion);
|
||||
|
||||
default:
|
||||
return ZSurveyQuestion.parse(clonedQuestion);
|
||||
}
|
||||
if (question.type === "nps") {
|
||||
(clonedQuestion as TSurveyNPSQuestion).lowerLabel = createI18nString(
|
||||
question.lowerLabel ?? "",
|
||||
languages
|
||||
);
|
||||
(clonedQuestion as TSurveyNPSQuestion).upperLabel = createI18nString(
|
||||
question.upperLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
if (question.type === "rating") {
|
||||
(clonedQuestion as TSurveyRatingQuestion).lowerLabel = createI18nString(
|
||||
question.lowerLabel ?? "",
|
||||
languages
|
||||
);
|
||||
(clonedQuestion as TSurveyRatingQuestion).upperLabel = createI18nString(
|
||||
question.upperLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
return clonedQuestion;
|
||||
};
|
||||
|
||||
export const extractLanguageIds = (languages: TLanguage[]): string[] => {
|
||||
@@ -172,14 +251,13 @@ export const extractLanguageIds = (languages: TLanguage[]): string[] => {
|
||||
// Function to translate an entire survey
|
||||
export const translateSurvey = (
|
||||
survey: Pick<TSurvey, "questions" | "welcomeCard" | "thankYouCard">,
|
||||
surveyLanguages: TLanguage[]
|
||||
languageCodes: string[]
|
||||
): Pick<TSurvey, "questions" | "welcomeCard" | "thankYouCard"> => {
|
||||
const languages = extractLanguageIds(surveyLanguages);
|
||||
const translatedQuestions = survey.questions.map((question) => {
|
||||
return translateQuestion(question, languages);
|
||||
return translateQuestion(question, languageCodes);
|
||||
});
|
||||
const translatedWelcomeCard = translateWelcomeCard(survey.welcomeCard, languages);
|
||||
const translatedThankYouCard = translateThankYouCard(survey.thankYouCard, languages);
|
||||
const translatedWelcomeCard = translateWelcomeCard(survey.welcomeCard, languageCodes);
|
||||
const translatedThankYouCard = translateThankYouCard(survey.thankYouCard, languageCodes);
|
||||
const translatedSurvey = structuredClone(survey);
|
||||
return {
|
||||
...translatedSurvey,
|
||||
@@ -188,3 +266,12 @@ export const translateSurvey = (
|
||||
thankYouCard: translatedThankYouCard,
|
||||
};
|
||||
};
|
||||
|
||||
export const hasStringSubheaders = (questions: TSurveyQuestions): boolean => {
|
||||
for (const question of questions) {
|
||||
if (typeof question.subheader !== "undefined") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"post-install": "pnpm generate",
|
||||
"predev": "pnpm generate",
|
||||
"data-migration:v1.6": "ts-node ./migrations/20240207041922_advanced_targeting/data-migration.ts",
|
||||
"data-migration:mls": "ts-node ./migrations/20240318050527_add_languages_and_survey_languages/data-migration.ts"
|
||||
"data-migration:mls": "ts-node ./migrations/20240318050527_add_languages_and_survey_languages/data-migration.ts",
|
||||
"data-migration:mls-fix": "ts-node ./migrations/20240318050527_add_languages_and_survey_languages/data-migration-fix.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.11.0",
|
||||
|
||||
@@ -195,7 +195,6 @@ export const mockConsentQuestion: TSurveyConsentQuestion = {
|
||||
label: {
|
||||
default: "I agree to the terms and conditions",
|
||||
},
|
||||
dismissButtonLabel: "Skip",
|
||||
id: "av561aoif3i2hjlsl6krnsfm",
|
||||
type: TSurveyQuestionType.Consent,
|
||||
isDraft: true,
|
||||
@@ -449,14 +448,12 @@ export const mockTranslatedConsentQuestion = {
|
||||
...mockConsentQuestion,
|
||||
headline: { default: "Terms and Conditions", de: "" },
|
||||
label: { default: "I agree to the terms and conditions", de: "" },
|
||||
dismissButtonLabel: "Skip",
|
||||
};
|
||||
|
||||
export const mockLegacyConsentQuestion = {
|
||||
...mockConsentQuestion,
|
||||
headline: "Terms and Conditions",
|
||||
label: "I agree to the terms and conditions",
|
||||
dismissButtonLabel: "Skip",
|
||||
};
|
||||
|
||||
export const mockTranslatedDateQuestion = {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { TSurveyLanguage } from "@formbricks/types/surveys";
|
||||
|
||||
import {
|
||||
mockLegacySurvey,
|
||||
mockSurvey,
|
||||
@@ -64,31 +62,8 @@ describe("translateThankYouCard", () => {
|
||||
|
||||
describe("translateSurvey", () => {
|
||||
it("should translate all questions of a Survey", () => {
|
||||
const languages: TSurveyLanguage[] = [
|
||||
{
|
||||
default: true,
|
||||
enabled: true,
|
||||
language: {
|
||||
id: "rp2di001zicbm3mk8je1ue9u",
|
||||
code: "en",
|
||||
alias: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
},
|
||||
{
|
||||
default: false,
|
||||
enabled: true,
|
||||
language: {
|
||||
id: "cuuxfzls09sjkueg6lm6n7i0",
|
||||
code: "de",
|
||||
alias: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
},
|
||||
];
|
||||
const translatedSurvey = translateSurvey(mockSurvey, languages);
|
||||
const languageCodes = ["default", "de"];
|
||||
const translatedSurvey = translateSurvey(mockSurvey, languageCodes);
|
||||
expect(translatedSurvey).toEqual(mockTranslatedSurvey);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,40 @@
|
||||
import {
|
||||
TLegacySurveyChoice,
|
||||
TLegacySurveyQuestion,
|
||||
TLegacySurveyThankYouCard,
|
||||
TLegacySurveyWelcomeCard,
|
||||
} from "@formbricks/types/LegacySurvey";
|
||||
import { TLanguage } from "@formbricks/types/product";
|
||||
import {
|
||||
TI18nString,
|
||||
TSurvey,
|
||||
TSurveyCTAQuestion,
|
||||
TSurveyChoice,
|
||||
TSurveyConsentQuestion,
|
||||
TSurveyLanguage,
|
||||
TSurveyMultipleChoiceMultiQuestion,
|
||||
TSurveyMultipleChoiceSingleQuestion,
|
||||
TSurveyNPSQuestion,
|
||||
TSurveyOpenTextQuestion,
|
||||
TSurveyQuestion,
|
||||
TSurveyRatingQuestion,
|
||||
TSurveyThankYouCard,
|
||||
TSurveyWelcomeCard,
|
||||
ZSurveyCTAQuestion,
|
||||
ZSurveyCalQuestion,
|
||||
ZSurveyConsentQuestion,
|
||||
ZSurveyFileUploadQuestion,
|
||||
ZSurveyMultipleChoiceMultiQuestion,
|
||||
ZSurveyMultipleChoiceSingleQuestion,
|
||||
ZSurveyNPSQuestion,
|
||||
ZSurveyOpenTextQuestion,
|
||||
ZSurveyPictureSelectionQuestion,
|
||||
ZSurveyQuestion,
|
||||
ZSurveyRatingQuestion,
|
||||
ZSurveyThankYouCard,
|
||||
ZSurveyWelcomeCard,
|
||||
} from "@formbricks/types/surveys";
|
||||
import {
|
||||
TSurvey,
|
||||
TSurveyLanguage,
|
||||
TSurveyMultipleChoiceMultiQuestion,
|
||||
TSurveyQuestion,
|
||||
} from "@formbricks/types/surveys";
|
||||
|
||||
// Helper function to create an i18nString from a regular string.
|
||||
@@ -59,7 +83,7 @@ export function isI18nObject(obj: any): obj is TI18nString {
|
||||
return (
|
||||
obj !== null &&
|
||||
typeof obj === "object" &&
|
||||
Object.values(obj).every((value) => typeof value === "string") &&
|
||||
Object.values(obj).every((value) => typeof value !== "undefined") &&
|
||||
Object.keys(obj).includes("default")
|
||||
);
|
||||
}
|
||||
@@ -92,180 +116,198 @@ export const getEnabledLanguages = (surveyLanguages: TSurveyLanguage[]) => {
|
||||
return surveyLanguages.filter((surveyLanguage) => surveyLanguage.enabled);
|
||||
};
|
||||
|
||||
const translateChoice = (choice: TSurveyChoice | TLegacySurveyChoice, languages: string[]): TSurveyChoice => {
|
||||
if (typeof choice.label !== "undefined") {
|
||||
return {
|
||||
...choice,
|
||||
label: createI18nString(choice.label, languages),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...choice,
|
||||
label: choice.label,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// LGEGACY
|
||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||
export const translateWelcomeCard = (
|
||||
welcomeCard: TSurveyWelcomeCard,
|
||||
languages: string[],
|
||||
targetLanguageCode?: string
|
||||
welcomeCard: TSurveyWelcomeCard | TLegacySurveyWelcomeCard,
|
||||
languages: string[]
|
||||
): TSurveyWelcomeCard => {
|
||||
const clonedWelcomeCard = structuredClone(welcomeCard);
|
||||
if (welcomeCard.headline) {
|
||||
clonedWelcomeCard.headline = createI18nString(welcomeCard.headline, languages, targetLanguageCode);
|
||||
if (typeof welcomeCard.headline !== "undefined") {
|
||||
clonedWelcomeCard.headline = createI18nString(welcomeCard.headline ?? "", languages);
|
||||
}
|
||||
if (welcomeCard.html) {
|
||||
clonedWelcomeCard.html = createI18nString(welcomeCard.html, languages, targetLanguageCode);
|
||||
if (typeof welcomeCard.html !== "undefined") {
|
||||
clonedWelcomeCard.html = createI18nString(welcomeCard.html ?? "", languages);
|
||||
}
|
||||
if (clonedWelcomeCard.buttonLabel) {
|
||||
clonedWelcomeCard.buttonLabel = createI18nString(
|
||||
clonedWelcomeCard.buttonLabel,
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
if (typeof welcomeCard.buttonLabel !== "undefined") {
|
||||
clonedWelcomeCard.buttonLabel = createI18nString(clonedWelcomeCard.buttonLabel ?? "", languages);
|
||||
}
|
||||
|
||||
return clonedWelcomeCard;
|
||||
return ZSurveyWelcomeCard.parse(clonedWelcomeCard);
|
||||
};
|
||||
|
||||
// LGEGACY
|
||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||
export const translateThankYouCard = (
|
||||
thankYouCard: TSurveyThankYouCard,
|
||||
languages: string[],
|
||||
targetLanguageCode?: string
|
||||
thankYouCard: TSurveyThankYouCard | TLegacySurveyThankYouCard,
|
||||
languages: string[]
|
||||
): TSurveyThankYouCard => {
|
||||
const clonedThankYouCard = structuredClone(thankYouCard);
|
||||
if (thankYouCard.headline) {
|
||||
clonedThankYouCard.headline = createI18nString(thankYouCard.headline, languages, targetLanguageCode);
|
||||
}
|
||||
if (thankYouCard.subheader) {
|
||||
clonedThankYouCard.subheader = createI18nString(thankYouCard.subheader, languages, targetLanguageCode);
|
||||
}
|
||||
if (thankYouCard.buttonLabel) {
|
||||
clonedThankYouCard.buttonLabel = createI18nString(
|
||||
thankYouCard.buttonLabel,
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
|
||||
if (typeof thankYouCard.headline !== "undefined") {
|
||||
clonedThankYouCard.headline = createI18nString(thankYouCard.headline ?? "", languages);
|
||||
}
|
||||
|
||||
return clonedThankYouCard;
|
||||
if (typeof thankYouCard.subheader !== "undefined") {
|
||||
clonedThankYouCard.subheader = createI18nString(thankYouCard.subheader ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof clonedThankYouCard.buttonLabel !== "undefined") {
|
||||
clonedThankYouCard.buttonLabel = createI18nString(thankYouCard.buttonLabel ?? "", languages);
|
||||
}
|
||||
return ZSurveyThankYouCard.parse(clonedThankYouCard);
|
||||
};
|
||||
|
||||
// LGEGACY
|
||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||
export const translateQuestion = (
|
||||
question: TSurveyQuestion,
|
||||
languages: string[],
|
||||
targetLanguageCode?: string
|
||||
) => {
|
||||
question: TLegacySurveyQuestion | TSurveyQuestion,
|
||||
languages: string[]
|
||||
): TSurveyQuestion => {
|
||||
// Clone the question to avoid mutating the original
|
||||
const clonedQuestion = structuredClone(question);
|
||||
|
||||
clonedQuestion.headline = createI18nString(question.headline, languages, targetLanguageCode);
|
||||
if (clonedQuestion.subheader) {
|
||||
clonedQuestion.subheader = createI18nString(question.subheader ?? "", languages, targetLanguageCode);
|
||||
//common question properties
|
||||
if (typeof question.headline !== "undefined") {
|
||||
clonedQuestion.headline = createI18nString(question.headline ?? "", languages);
|
||||
}
|
||||
|
||||
if (clonedQuestion.buttonLabel) {
|
||||
clonedQuestion.buttonLabel = createI18nString(question.buttonLabel ?? "", languages, targetLanguageCode);
|
||||
if (typeof question.subheader !== "undefined") {
|
||||
clonedQuestion.subheader = createI18nString(question.subheader ?? "", languages);
|
||||
}
|
||||
|
||||
if (clonedQuestion.backButtonLabel) {
|
||||
clonedQuestion.backButtonLabel = createI18nString(
|
||||
question.backButtonLabel ?? "",
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
if (typeof question.buttonLabel !== "undefined") {
|
||||
clonedQuestion.buttonLabel = createI18nString(question.buttonLabel ?? "", languages);
|
||||
}
|
||||
|
||||
if (question.type === "multipleChoiceSingle" || question.type === "multipleChoiceMulti") {
|
||||
(clonedQuestion as TSurveyMultipleChoiceMultiQuestion | TSurveyMultipleChoiceMultiQuestion).choices =
|
||||
question.choices.map((choice) => ({
|
||||
...choice,
|
||||
label: createI18nString(choice.label, languages, targetLanguageCode),
|
||||
}));
|
||||
(
|
||||
clonedQuestion as TSurveyMultipleChoiceMultiQuestion | TSurveyMultipleChoiceMultiQuestion
|
||||
).otherOptionPlaceholder = question.otherOptionPlaceholder
|
||||
? createI18nString(question.otherOptionPlaceholder, languages, targetLanguageCode)
|
||||
: undefined;
|
||||
if (typeof question.backButtonLabel !== "undefined") {
|
||||
clonedQuestion.backButtonLabel = createI18nString(question.backButtonLabel ?? "", languages);
|
||||
}
|
||||
if (question.type === "openText") {
|
||||
if (question.placeholder) {
|
||||
(clonedQuestion as TSurveyOpenTextQuestion).placeholder = createI18nString(
|
||||
question.placeholder,
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
}
|
||||
}
|
||||
if (question.type === "cta") {
|
||||
if (question.dismissButtonLabel) {
|
||||
(clonedQuestion as TSurveyCTAQuestion).dismissButtonLabel = createI18nString(
|
||||
question.dismissButtonLabel,
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
}
|
||||
if (question.html) {
|
||||
(clonedQuestion as TSurveyCTAQuestion).html = createI18nString(
|
||||
question.html,
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
}
|
||||
}
|
||||
if (question.type === "consent") {
|
||||
if (question.html) {
|
||||
(clonedQuestion as TSurveyConsentQuestion).html = createI18nString(
|
||||
question.html,
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
}
|
||||
|
||||
if (question.label) {
|
||||
(clonedQuestion as TSurveyConsentQuestion).label = createI18nString(
|
||||
question.label,
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
}
|
||||
switch (question.type) {
|
||||
case "openText":
|
||||
if (typeof question.placeholder !== "undefined") {
|
||||
(clonedQuestion as TSurveyOpenTextQuestion).placeholder = createI18nString(
|
||||
question.placeholder ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
return ZSurveyOpenTextQuestion.parse(clonedQuestion);
|
||||
|
||||
case "multipleChoiceSingle":
|
||||
case "multipleChoiceMulti":
|
||||
(clonedQuestion as TSurveyMultipleChoiceSingleQuestion | TSurveyMultipleChoiceMultiQuestion).choices =
|
||||
question.choices.map((choice) => {
|
||||
return translateChoice(choice, languages);
|
||||
});
|
||||
if (
|
||||
typeof (clonedQuestion as TSurveyMultipleChoiceSingleQuestion | TSurveyMultipleChoiceMultiQuestion)
|
||||
.otherOptionPlaceholder !== "undefined"
|
||||
) {
|
||||
(
|
||||
clonedQuestion as TSurveyMultipleChoiceSingleQuestion | TSurveyMultipleChoiceMultiQuestion
|
||||
).otherOptionPlaceholder = createI18nString(question.otherOptionPlaceholder ?? "", languages);
|
||||
}
|
||||
if (question.type === "multipleChoiceSingle") {
|
||||
return ZSurveyMultipleChoiceSingleQuestion.parse(clonedQuestion);
|
||||
} else return ZSurveyMultipleChoiceMultiQuestion.parse(clonedQuestion);
|
||||
|
||||
case "cta":
|
||||
if (typeof question.dismissButtonLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyCTAQuestion).dismissButtonLabel = createI18nString(
|
||||
question.dismissButtonLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
if (typeof question.html !== "undefined") {
|
||||
(clonedQuestion as TSurveyCTAQuestion).html = createI18nString(question.html ?? "", languages);
|
||||
}
|
||||
return ZSurveyCTAQuestion.parse(clonedQuestion);
|
||||
|
||||
case "consent":
|
||||
if (typeof question.html !== "undefined") {
|
||||
(clonedQuestion as TSurveyConsentQuestion).html = createI18nString(question.html ?? "", languages);
|
||||
}
|
||||
|
||||
if (typeof question.label !== "undefined") {
|
||||
(clonedQuestion as TSurveyConsentQuestion).label = createI18nString(question.label ?? "", languages);
|
||||
}
|
||||
return ZSurveyConsentQuestion.parse(clonedQuestion);
|
||||
|
||||
case "nps":
|
||||
if (typeof question.lowerLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyNPSQuestion).lowerLabel = createI18nString(
|
||||
question.lowerLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
if (typeof question.upperLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyNPSQuestion).upperLabel = createI18nString(
|
||||
question.upperLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
return ZSurveyNPSQuestion.parse(clonedQuestion);
|
||||
|
||||
case "rating":
|
||||
if (typeof question.lowerLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyRatingQuestion).lowerLabel = createI18nString(
|
||||
question.lowerLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof question.upperLabel !== "undefined") {
|
||||
(clonedQuestion as TSurveyRatingQuestion).upperLabel = createI18nString(
|
||||
question.upperLabel ?? "",
|
||||
languages
|
||||
);
|
||||
}
|
||||
return ZSurveyRatingQuestion.parse(clonedQuestion);
|
||||
|
||||
case "fileUpload":
|
||||
return ZSurveyFileUploadQuestion.parse(clonedQuestion);
|
||||
|
||||
case "pictureSelection":
|
||||
return ZSurveyPictureSelectionQuestion.parse(clonedQuestion);
|
||||
|
||||
case "cal":
|
||||
return ZSurveyCalQuestion.parse(clonedQuestion);
|
||||
|
||||
default:
|
||||
return ZSurveyQuestion.parse(clonedQuestion);
|
||||
}
|
||||
if (question.type === "nps") {
|
||||
(clonedQuestion as TSurveyNPSQuestion).lowerLabel = createI18nString(
|
||||
question.lowerLabel ?? "",
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
(clonedQuestion as TSurveyNPSQuestion).upperLabel = createI18nString(
|
||||
question.upperLabel ?? "",
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
}
|
||||
if (question.type === "rating") {
|
||||
(clonedQuestion as TSurveyRatingQuestion).lowerLabel = createI18nString(
|
||||
question.lowerLabel ?? "",
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
(clonedQuestion as TSurveyRatingQuestion).upperLabel = createI18nString(
|
||||
question.upperLabel ?? "",
|
||||
languages,
|
||||
targetLanguageCode
|
||||
);
|
||||
}
|
||||
return clonedQuestion;
|
||||
};
|
||||
|
||||
export const extractLanguageIds = (languages: TLanguage[]): string[] => {
|
||||
return languages.map((language) => language.code);
|
||||
};
|
||||
|
||||
// LGEGACY
|
||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||
export const translateSurvey = (
|
||||
survey: TSurvey,
|
||||
surveyLanguages: TSurveyLanguage[],
|
||||
targetLanguageCode?: string
|
||||
): TSurvey => {
|
||||
const languages = extractLanguageCodes(surveyLanguages);
|
||||
|
||||
survey: Pick<TSurvey, "questions" | "welcomeCard" | "thankYouCard">,
|
||||
languageCodes: string[]
|
||||
): Pick<TSurvey, "questions" | "welcomeCard" | "thankYouCard"> => {
|
||||
const translatedQuestions = survey.questions.map((question) => {
|
||||
return translateQuestion(question, languages, targetLanguageCode);
|
||||
return translateQuestion(question, languageCodes);
|
||||
});
|
||||
const translatedWelcomeCard =
|
||||
survey.welcomeCard && translateWelcomeCard(survey.welcomeCard, languages, targetLanguageCode);
|
||||
const translatedThankYouCard =
|
||||
survey.thankYouCard && translateThankYouCard(survey.thankYouCard, languages, targetLanguageCode);
|
||||
const translatedWelcomeCard = translateWelcomeCard(survey.welcomeCard, languageCodes);
|
||||
const translatedThankYouCard = translateThankYouCard(survey.thankYouCard, languageCodes);
|
||||
const translatedSurvey = structuredClone(survey);
|
||||
return {
|
||||
...translatedSurvey,
|
||||
|
||||
@@ -19,7 +19,6 @@ import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
import { displayCache } from "../display/cache";
|
||||
import { getDisplaysByPersonId } from "../display/service";
|
||||
import { reverseTranslateSurvey } from "../i18n/reverseTranslation";
|
||||
import { translateSurvey } from "../i18n/utils";
|
||||
import { personCache } from "../person/cache";
|
||||
import { getPerson } from "../person/service";
|
||||
import { productCache } from "../product/cache";
|
||||
@@ -366,30 +365,6 @@ export const transformToLegacySurvey = async (
|
||||
return formatDateFields(transformedSurvey, ZLegacySurvey);
|
||||
};
|
||||
|
||||
export const transformSurveyToSpecificLanguage = async (
|
||||
survey: TSurvey,
|
||||
targetLanguageCode?: string
|
||||
): Promise<TSurvey> => {
|
||||
// if target language code is not available, it will be transformed to default language
|
||||
const transformedSurvey = await unstable_cache(
|
||||
async () => {
|
||||
if (!survey.languages || survey.languages.length === 0) {
|
||||
//survey do not have any translations
|
||||
return survey;
|
||||
}
|
||||
return translateSurvey(survey, [], targetLanguageCode);
|
||||
},
|
||||
[`transformSurveyToSpecificLanguage-${survey}-${targetLanguageCode}`],
|
||||
{
|
||||
tags: [surveyCache.tag.byId(survey.id)],
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
// since the unstable_cache function does not support deserialization of dates, we need to manually deserialize them
|
||||
// https://github.com/vercel/next.js/issues/51613
|
||||
return formatDateFields(transformedSurvey, ZSurvey);
|
||||
};
|
||||
|
||||
export const getSurveyCount = async (environmentId: string): Promise<number> => {
|
||||
const count = await unstable_cache(
|
||||
async () => {
|
||||
|
||||
@@ -40,7 +40,6 @@ export const ZLegacySurveyConsentQuestion = ZLegacySurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionType.Consent),
|
||||
html: z.string().optional(),
|
||||
label: z.string(),
|
||||
dismissButtonLabel: z.string().optional(),
|
||||
placeholder: z.string().optional(),
|
||||
logic: z.array(ZSurveyConsentLogic).optional(),
|
||||
});
|
||||
|
||||
@@ -272,7 +272,6 @@ export const ZSurveyConsentQuestion = ZSurveyQuestionBase.extend({
|
||||
type: z.literal(TSurveyQuestionType.Consent),
|
||||
html: ZI18nString.optional(),
|
||||
label: ZI18nString,
|
||||
dismissButtonLabel: z.string().optional(),
|
||||
placeholder: z.string().optional(),
|
||||
logic: z.array(ZSurveyConsentLogic).optional(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user