mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-23 05:17:49 -05:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3768055a6a | |||
| 74a21f03f2 | |||
| c148e3bd74 | |||
| f27f55a655 | |||
| d7237b81da |
+1
-1
@@ -237,7 +237,7 @@ export default function MultipleChoiceMultiForm({
|
|||||||
{question.choices &&
|
{question.choices &&
|
||||||
question.choices.map((choice, choiceIdx) => (
|
question.choices.map((choice, choiceIdx) => (
|
||||||
<div key={choiceIdx} className="inline-flex w-full items-center">
|
<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
|
<QuestionFormInput
|
||||||
key={choice.id}
|
key={choice.id}
|
||||||
id={`choice-${choiceIdx}`}
|
id={`choice-${choiceIdx}`}
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@ const validationRules = {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (const field of fieldsToValidate) {
|
for (const field of fieldsToValidate) {
|
||||||
if (question[field] && question[field][defaultLanguageCode]) {
|
if (question[field] && typeof question[field][defaultLanguageCode] !== "undefined") {
|
||||||
isValid =
|
isValid =
|
||||||
isValid && isLabelValidForAllLanguages(question[field], languages) && isValidCTADismissLabel;
|
isValid && isLabelValidForAllLanguages(question[field], languages) && isValidCTADismissLabel;
|
||||||
}
|
}
|
||||||
|
|||||||
+49
@@ -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());
|
||||||
+169
-82
@@ -1,13 +1,35 @@
|
|||||||
|
import {
|
||||||
|
TLegacySurveyChoice,
|
||||||
|
TLegacySurveyQuestion,
|
||||||
|
TLegacySurveyThankYouCard,
|
||||||
|
TLegacySurveyWelcomeCard,
|
||||||
|
} from "@formbricks/types/LegacySurvey";
|
||||||
import { TLanguage } from "@formbricks/types/product";
|
import { TLanguage } from "@formbricks/types/product";
|
||||||
import {
|
import {
|
||||||
TI18nString,
|
TI18nString,
|
||||||
TSurveyCTAQuestion,
|
TSurveyCTAQuestion,
|
||||||
|
TSurveyChoice,
|
||||||
TSurveyConsentQuestion,
|
TSurveyConsentQuestion,
|
||||||
|
TSurveyMultipleChoiceSingleQuestion,
|
||||||
TSurveyNPSQuestion,
|
TSurveyNPSQuestion,
|
||||||
TSurveyOpenTextQuestion,
|
TSurveyOpenTextQuestion,
|
||||||
|
TSurveyQuestions,
|
||||||
TSurveyRatingQuestion,
|
TSurveyRatingQuestion,
|
||||||
TSurveyThankYouCard,
|
TSurveyThankYouCard,
|
||||||
TSurveyWelcomeCard,
|
TSurveyWelcomeCard,
|
||||||
|
ZSurveyCTAQuestion,
|
||||||
|
ZSurveyCalQuestion,
|
||||||
|
ZSurveyConsentQuestion,
|
||||||
|
ZSurveyFileUploadQuestion,
|
||||||
|
ZSurveyMultipleChoiceMultiQuestion,
|
||||||
|
ZSurveyMultipleChoiceSingleQuestion,
|
||||||
|
ZSurveyNPSQuestion,
|
||||||
|
ZSurveyOpenTextQuestion,
|
||||||
|
ZSurveyPictureSelectionQuestion,
|
||||||
|
ZSurveyQuestion,
|
||||||
|
ZSurveyRatingQuestion,
|
||||||
|
ZSurveyThankYouCard,
|
||||||
|
ZSurveyWelcomeCard,
|
||||||
} from "@formbricks/types/surveys";
|
} from "@formbricks/types/surveys";
|
||||||
import { TSurvey, TSurveyMultipleChoiceMultiQuestion, TSurveyQuestion } from "@formbricks/types/surveys";
|
import { TSurvey, TSurveyMultipleChoiceMultiQuestion, TSurveyQuestion } from "@formbricks/types/surveys";
|
||||||
|
|
||||||
@@ -49,120 +71,176 @@ export const createI18nString = (text: string | TI18nString, languages: string[]
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Function to translate a choice label
|
// Function to translate a choice label
|
||||||
const translateChoice = (choice: any, languages: string[]) => {
|
const translateChoice = (choice: TSurveyChoice | TLegacySurveyChoice, languages: string[]): TSurveyChoice => {
|
||||||
// Assuming choice is a simple object and choice.label is a string.
|
if (typeof choice.label === "string") {
|
||||||
return {
|
return {
|
||||||
...choice,
|
...choice,
|
||||||
label: createI18nString(choice.label, languages),
|
label: createI18nString(choice.label, languages),
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...choice,
|
||||||
|
label: choice.label,
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const translateWelcomeCard = (
|
export const translateWelcomeCard = (
|
||||||
welcomeCard: TSurveyWelcomeCard,
|
welcomeCard: TSurveyWelcomeCard | TLegacySurveyWelcomeCard,
|
||||||
languages: string[]
|
languages: string[]
|
||||||
): TSurveyWelcomeCard => {
|
): TSurveyWelcomeCard => {
|
||||||
const clonedWelcomeCard = structuredClone(welcomeCard);
|
const clonedWelcomeCard = structuredClone(welcomeCard);
|
||||||
clonedWelcomeCard.headline = createI18nString(welcomeCard.headline, languages);
|
if (typeof welcomeCard.headline === "string") {
|
||||||
clonedWelcomeCard.html = createI18nString(welcomeCard.html ?? "", languages);
|
clonedWelcomeCard.headline = createI18nString(welcomeCard.headline ?? "", languages);
|
||||||
if (clonedWelcomeCard.buttonLabel) {
|
}
|
||||||
clonedWelcomeCard.buttonLabel = createI18nString(clonedWelcomeCard.buttonLabel, languages);
|
if (typeof welcomeCard.html === "string") {
|
||||||
|
clonedWelcomeCard.html = createI18nString(welcomeCard.html ?? "", languages);
|
||||||
|
}
|
||||||
|
if (typeof welcomeCard.buttonLabel === "string") {
|
||||||
|
clonedWelcomeCard.buttonLabel = createI18nString(clonedWelcomeCard.buttonLabel ?? "", languages);
|
||||||
}
|
}
|
||||||
|
|
||||||
return clonedWelcomeCard;
|
return ZSurveyWelcomeCard.parse(clonedWelcomeCard);
|
||||||
};
|
};
|
||||||
|
|
||||||
const translateThankYouCard = (
|
const translateThankYouCard = (
|
||||||
thankYouCard: TSurveyThankYouCard,
|
thankYouCard: TSurveyThankYouCard | TLegacySurveyThankYouCard,
|
||||||
languages: string[]
|
languages: string[]
|
||||||
): TSurveyThankYouCard => {
|
): TSurveyThankYouCard => {
|
||||||
const clonedThankYouCard = structuredClone(thankYouCard);
|
const clonedThankYouCard = structuredClone(thankYouCard);
|
||||||
clonedThankYouCard.headline = createI18nString(
|
|
||||||
thankYouCard.headline ? thankYouCard.headline : "",
|
if (typeof thankYouCard.headline === "string") {
|
||||||
languages
|
clonedThankYouCard.headline = createI18nString(thankYouCard.headline ?? "", languages);
|
||||||
);
|
|
||||||
if (clonedThankYouCard.subheader) {
|
|
||||||
clonedThankYouCard.subheader = createI18nString(
|
|
||||||
thankYouCard.subheader ? thankYouCard.subheader : "",
|
|
||||||
languages
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return clonedThankYouCard;
|
if (typeof thankYouCard.subheader === "string") {
|
||||||
|
clonedThankYouCard.subheader = createI18nString(thankYouCard.subheader ?? "", languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof clonedThankYouCard.buttonLabel === "string") {
|
||||||
|
clonedThankYouCard.buttonLabel = createI18nString(thankYouCard.buttonLabel ?? "", languages);
|
||||||
|
}
|
||||||
|
return ZSurveyThankYouCard.parse(clonedThankYouCard);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function that will translate a single question
|
// 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
|
// Clone the question to avoid mutating the original
|
||||||
const clonedQuestion = structuredClone(question);
|
const clonedQuestion = structuredClone(question);
|
||||||
|
|
||||||
clonedQuestion.headline = createI18nString(question.headline, languages);
|
//common question properties
|
||||||
if (clonedQuestion.subheader) {
|
if (typeof question.headline === "string") {
|
||||||
|
clonedQuestion.headline = createI18nString(question.headline ?? "", languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof question.subheader === "string") {
|
||||||
clonedQuestion.subheader = createI18nString(question.subheader ?? "", languages);
|
clonedQuestion.subheader = createI18nString(question.subheader ?? "", languages);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clonedQuestion.buttonLabel) {
|
if (typeof question.buttonLabel === "string") {
|
||||||
clonedQuestion.buttonLabel = createI18nString(question.buttonLabel ?? "", languages);
|
clonedQuestion.buttonLabel = createI18nString(question.buttonLabel ?? "", languages);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clonedQuestion.backButtonLabel) {
|
if (typeof question.backButtonLabel === "string") {
|
||||||
clonedQuestion.backButtonLabel = createI18nString(question.backButtonLabel ?? "", languages);
|
clonedQuestion.backButtonLabel = createI18nString(question.backButtonLabel ?? "", languages);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (question.type === "multipleChoiceSingle" || question.type === "multipleChoiceMulti") {
|
switch (question.type) {
|
||||||
(clonedQuestion as TSurveyMultipleChoiceMultiQuestion | TSurveyMultipleChoiceMultiQuestion).choices =
|
case "openText":
|
||||||
question.choices.map((choice) => translateChoice(structuredClone(choice), languages));
|
if (typeof question.placeholder === "string") {
|
||||||
(
|
(clonedQuestion as TSurveyOpenTextQuestion).placeholder = createI18nString(
|
||||||
clonedQuestion as TSurveyMultipleChoiceMultiQuestion | TSurveyMultipleChoiceMultiQuestion
|
question.placeholder ?? "",
|
||||||
).otherOptionPlaceholder = question.otherOptionPlaceholder
|
languages
|
||||||
? createI18nString(question.otherOptionPlaceholder, languages)
|
);
|
||||||
: undefined;
|
}
|
||||||
}
|
return ZSurveyOpenTextQuestion.parse(clonedQuestion);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (question.label) {
|
case "multipleChoiceSingle":
|
||||||
(clonedQuestion as TSurveyConsentQuestion).label = createI18nString(question.label, languages);
|
case "multipleChoiceMulti":
|
||||||
}
|
(clonedQuestion as TSurveyMultipleChoiceSingleQuestion | TSurveyMultipleChoiceMultiQuestion).choices =
|
||||||
|
question.choices.map((choice) => {
|
||||||
|
return translateChoice(choice, languages);
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
typeof (clonedQuestion as TSurveyMultipleChoiceSingleQuestion | TSurveyMultipleChoiceMultiQuestion)
|
||||||
|
.otherOptionPlaceholder === "string"
|
||||||
|
) {
|
||||||
|
(
|
||||||
|
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 === "string") {
|
||||||
|
(clonedQuestion as TSurveyCTAQuestion).dismissButtonLabel = createI18nString(
|
||||||
|
question.dismissButtonLabel ?? "",
|
||||||
|
languages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof question.html === "string") {
|
||||||
|
(clonedQuestion as TSurveyCTAQuestion).html = createI18nString(question.html ?? "", languages);
|
||||||
|
}
|
||||||
|
return ZSurveyCTAQuestion.parse(clonedQuestion);
|
||||||
|
|
||||||
|
case "consent":
|
||||||
|
if (typeof question.html === "string") {
|
||||||
|
(clonedQuestion as TSurveyConsentQuestion).html = createI18nString(question.html ?? "", languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof question.label === "string") {
|
||||||
|
(clonedQuestion as TSurveyConsentQuestion).label = createI18nString(question.label ?? "", languages);
|
||||||
|
}
|
||||||
|
return ZSurveyConsentQuestion.parse(clonedQuestion);
|
||||||
|
|
||||||
|
case "nps":
|
||||||
|
if (typeof question.lowerLabel === "string") {
|
||||||
|
(clonedQuestion as TSurveyNPSQuestion).lowerLabel = createI18nString(
|
||||||
|
question.lowerLabel ?? "",
|
||||||
|
languages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof question.upperLabel === "string") {
|
||||||
|
(clonedQuestion as TSurveyNPSQuestion).upperLabel = createI18nString(
|
||||||
|
question.upperLabel ?? "",
|
||||||
|
languages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ZSurveyNPSQuestion.parse(clonedQuestion);
|
||||||
|
|
||||||
|
case "rating":
|
||||||
|
if (typeof question.lowerLabel === "string") {
|
||||||
|
(clonedQuestion as TSurveyRatingQuestion).lowerLabel = createI18nString(
|
||||||
|
question.lowerLabel ?? "",
|
||||||
|
languages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof question.upperLabel === "string") {
|
||||||
|
(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[] => {
|
export const extractLanguageIds = (languages: TLanguage[]): string[] => {
|
||||||
@@ -188,3 +266,12 @@ export const translateSurvey = (
|
|||||||
thankYouCard: translatedThankYouCard,
|
thankYouCard: translatedThankYouCard,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const hasStringSubheaders = (questions: TSurveyQuestions): boolean => {
|
||||||
|
for (const question of questions) {
|
||||||
|
if (typeof question.subheader === "string") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
"post-install": "pnpm generate",
|
"post-install": "pnpm generate",
|
||||||
"predev": "pnpm generate",
|
"predev": "pnpm generate",
|
||||||
"data-migration:v1.6": "ts-node ./migrations/20240207041922_advanced_targeting/data-migration.ts",
|
"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-migration2:mls": "ts-node ./migrations/20240318050527_add_languages_and_survey_languages/data-migration2.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^5.11.0",
|
"@prisma/client": "^5.11.0",
|
||||||
|
|||||||
+187
-144
@@ -1,16 +1,40 @@
|
|||||||
|
import {
|
||||||
|
TLegacySurveyChoice,
|
||||||
|
TLegacySurveyQuestion,
|
||||||
|
TLegacySurveyThankYouCard,
|
||||||
|
TLegacySurveyWelcomeCard,
|
||||||
|
} from "@formbricks/types/LegacySurvey";
|
||||||
|
import { TLanguage } from "@formbricks/types/product";
|
||||||
import {
|
import {
|
||||||
TI18nString,
|
TI18nString,
|
||||||
TSurvey,
|
|
||||||
TSurveyCTAQuestion,
|
TSurveyCTAQuestion,
|
||||||
|
TSurveyChoice,
|
||||||
TSurveyConsentQuestion,
|
TSurveyConsentQuestion,
|
||||||
TSurveyLanguage,
|
TSurveyMultipleChoiceSingleQuestion,
|
||||||
TSurveyMultipleChoiceMultiQuestion,
|
|
||||||
TSurveyNPSQuestion,
|
TSurveyNPSQuestion,
|
||||||
TSurveyOpenTextQuestion,
|
TSurveyOpenTextQuestion,
|
||||||
TSurveyQuestion,
|
|
||||||
TSurveyRatingQuestion,
|
TSurveyRatingQuestion,
|
||||||
TSurveyThankYouCard,
|
TSurveyThankYouCard,
|
||||||
TSurveyWelcomeCard,
|
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";
|
} from "@formbricks/types/surveys";
|
||||||
|
|
||||||
// Helper function to create an i18nString from a regular string.
|
// Helper function to create an i18nString from a regular string.
|
||||||
@@ -92,180 +116,199 @@ export const getEnabledLanguages = (surveyLanguages: TSurveyLanguage[]) => {
|
|||||||
return surveyLanguages.filter((surveyLanguage) => surveyLanguage.enabled);
|
return surveyLanguages.filter((surveyLanguage) => surveyLanguage.enabled);
|
||||||
};
|
};
|
||||||
|
|
||||||
// LGEGACY
|
const translateChoice = (choice: TSurveyChoice | TLegacySurveyChoice, languages: string[]): TSurveyChoice => {
|
||||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
if (typeof choice.label === "string") {
|
||||||
export const translateWelcomeCard = (
|
return {
|
||||||
welcomeCard: TSurveyWelcomeCard,
|
...choice,
|
||||||
languages: string[],
|
label: createI18nString(choice.label, languages),
|
||||||
targetLanguageCode?: string
|
};
|
||||||
): TSurveyWelcomeCard => {
|
} else {
|
||||||
const clonedWelcomeCard = structuredClone(welcomeCard);
|
return {
|
||||||
if (welcomeCard.headline) {
|
...choice,
|
||||||
clonedWelcomeCard.headline = createI18nString(welcomeCard.headline, languages, targetLanguageCode);
|
label: choice.label,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (welcomeCard.html) {
|
|
||||||
clonedWelcomeCard.html = createI18nString(welcomeCard.html, languages, targetLanguageCode);
|
|
||||||
}
|
|
||||||
if (clonedWelcomeCard.buttonLabel) {
|
|
||||||
clonedWelcomeCard.buttonLabel = createI18nString(
|
|
||||||
clonedWelcomeCard.buttonLabel,
|
|
||||||
languages,
|
|
||||||
targetLanguageCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return clonedWelcomeCard;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// LGEGACY
|
// LGEGACY
|
||||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||||
export const translateThankYouCard = (
|
export const translateWelcomeCard = (
|
||||||
thankYouCard: TSurveyThankYouCard,
|
welcomeCard: TSurveyWelcomeCard | TLegacySurveyWelcomeCard,
|
||||||
languages: string[],
|
languages: string[]
|
||||||
targetLanguageCode?: string
|
): TSurveyWelcomeCard => {
|
||||||
): TSurveyThankYouCard => {
|
const clonedWelcomeCard = structuredClone(welcomeCard);
|
||||||
const clonedThankYouCard = structuredClone(thankYouCard);
|
if (typeof welcomeCard.headline === "string") {
|
||||||
if (thankYouCard.headline) {
|
clonedWelcomeCard.headline = createI18nString(welcomeCard.headline ?? "", languages);
|
||||||
clonedThankYouCard.headline = createI18nString(thankYouCard.headline, languages, targetLanguageCode);
|
|
||||||
}
|
}
|
||||||
if (thankYouCard.subheader) {
|
if (typeof welcomeCard.html === "string") {
|
||||||
clonedThankYouCard.subheader = createI18nString(thankYouCard.subheader, languages, targetLanguageCode);
|
clonedWelcomeCard.html = createI18nString(welcomeCard.html ?? "", languages);
|
||||||
}
|
}
|
||||||
if (thankYouCard.buttonLabel) {
|
if (typeof welcomeCard.buttonLabel === "string") {
|
||||||
clonedThankYouCard.buttonLabel = createI18nString(
|
clonedWelcomeCard.buttonLabel = createI18nString(clonedWelcomeCard.buttonLabel ?? "", languages);
|
||||||
thankYouCard.buttonLabel,
|
|
||||||
languages,
|
|
||||||
targetLanguageCode
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return clonedThankYouCard;
|
return ZSurveyWelcomeCard.parse(clonedWelcomeCard);
|
||||||
|
};
|
||||||
|
|
||||||
|
// LGEGACY
|
||||||
|
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||||
|
const translateThankYouCard = (
|
||||||
|
thankYouCard: TSurveyThankYouCard | TLegacySurveyThankYouCard,
|
||||||
|
languages: string[]
|
||||||
|
): TSurveyThankYouCard => {
|
||||||
|
const clonedThankYouCard = structuredClone(thankYouCard);
|
||||||
|
|
||||||
|
if (typeof thankYouCard.headline === "string") {
|
||||||
|
clonedThankYouCard.headline = createI18nString(thankYouCard.headline ?? "", languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof thankYouCard.subheader === "string") {
|
||||||
|
clonedThankYouCard.subheader = createI18nString(thankYouCard.subheader ?? "", languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof clonedThankYouCard.buttonLabel === "string") {
|
||||||
|
clonedThankYouCard.buttonLabel = createI18nString(thankYouCard.buttonLabel ?? "", languages);
|
||||||
|
}
|
||||||
|
return ZSurveyThankYouCard.parse(clonedThankYouCard);
|
||||||
};
|
};
|
||||||
|
|
||||||
// LGEGACY
|
// LGEGACY
|
||||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||||
export const translateQuestion = (
|
export const translateQuestion = (
|
||||||
question: TSurveyQuestion,
|
question: TLegacySurveyQuestion | TSurveyQuestion,
|
||||||
languages: string[],
|
languages: string[]
|
||||||
targetLanguageCode?: string
|
): TSurveyQuestion => {
|
||||||
) => {
|
|
||||||
// Clone the question to avoid mutating the original
|
// Clone the question to avoid mutating the original
|
||||||
const clonedQuestion = structuredClone(question);
|
const clonedQuestion = structuredClone(question);
|
||||||
|
|
||||||
clonedQuestion.headline = createI18nString(question.headline, languages, targetLanguageCode);
|
//common question properties
|
||||||
if (clonedQuestion.subheader) {
|
if (typeof question.headline === "string") {
|
||||||
clonedQuestion.subheader = createI18nString(question.subheader ?? "", languages, targetLanguageCode);
|
clonedQuestion.headline = createI18nString(question.headline ?? "", languages);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clonedQuestion.buttonLabel) {
|
if (typeof question.subheader === "string") {
|
||||||
clonedQuestion.buttonLabel = createI18nString(question.buttonLabel ?? "", languages, targetLanguageCode);
|
clonedQuestion.subheader = createI18nString(question.subheader ?? "", languages);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clonedQuestion.backButtonLabel) {
|
if (typeof question.buttonLabel === "string") {
|
||||||
clonedQuestion.backButtonLabel = createI18nString(
|
clonedQuestion.buttonLabel = createI18nString(question.buttonLabel ?? "", languages);
|
||||||
question.backButtonLabel ?? "",
|
|
||||||
languages,
|
|
||||||
targetLanguageCode
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (question.type === "multipleChoiceSingle" || question.type === "multipleChoiceMulti") {
|
if (typeof question.backButtonLabel === "string") {
|
||||||
(clonedQuestion as TSurveyMultipleChoiceMultiQuestion | TSurveyMultipleChoiceMultiQuestion).choices =
|
clonedQuestion.backButtonLabel = createI18nString(question.backButtonLabel ?? "", languages);
|
||||||
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 (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) {
|
const questionTypeHandlers = {
|
||||||
(clonedQuestion as TSurveyConsentQuestion).label = createI18nString(
|
"openText": handleOpenTextQuestion,
|
||||||
question.label,
|
"multipleChoiceSingle": handleMultipleChoiceSingleQuestion,
|
||||||
languages,
|
"multipleChoiceMulti": handleMultipleChoiceMultiQuestion,
|
||||||
targetLanguageCode
|
// ...
|
||||||
);
|
};
|
||||||
}
|
|
||||||
|
const handler = questionTypeHandlers[question.type];
|
||||||
|
if (handler) {
|
||||||
|
return handler(question, languages);
|
||||||
}
|
}
|
||||||
if (question.type === "nps") {
|
}
|
||||||
(clonedQuestion as TSurveyNPSQuestion).lowerLabel = createI18nString(
|
(clonedQuestion as TSurveyMultipleChoiceSingleQuestion | TSurveyMultipleChoiceMultiQuestion).choices =
|
||||||
question.lowerLabel ?? "",
|
question.choices.map((choice) => {
|
||||||
languages,
|
return translateChoice(choice, languages);
|
||||||
targetLanguageCode
|
});
|
||||||
);
|
if (
|
||||||
(clonedQuestion as TSurveyNPSQuestion).upperLabel = createI18nString(
|
typeof (clonedQuestion as TSurveyMultipleChoiceSingleQuestion | TSurveyMultipleChoiceMultiQuestion)
|
||||||
question.upperLabel ?? "",
|
.otherOptionPlaceholder === "string"
|
||||||
languages,
|
) {
|
||||||
targetLanguageCode
|
(
|
||||||
);
|
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 === "string") {
|
||||||
|
(clonedQuestion as TSurveyCTAQuestion).dismissButtonLabel = createI18nString(
|
||||||
|
question.dismissButtonLabel ?? "",
|
||||||
|
languages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof question.html === "string") {
|
||||||
|
(clonedQuestion as TSurveyCTAQuestion).html = createI18nString(question.html ?? "", languages);
|
||||||
|
}
|
||||||
|
return ZSurveyCTAQuestion.parse(clonedQuestion);
|
||||||
|
|
||||||
|
case "consent":
|
||||||
|
if (typeof question.html === "string") {
|
||||||
|
(clonedQuestion as TSurveyConsentQuestion).html = createI18nString(question.html ?? "", languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof question.label === "string") {
|
||||||
|
(clonedQuestion as TSurveyConsentQuestion).label = createI18nString(question.label ?? "", languages);
|
||||||
|
}
|
||||||
|
return ZSurveyConsentQuestion.parse(clonedQuestion);
|
||||||
|
|
||||||
|
case "nps":
|
||||||
|
if (typeof question.lowerLabel === "string") {
|
||||||
|
(clonedQuestion as TSurveyNPSQuestion).lowerLabel = createI18nString(
|
||||||
|
question.lowerLabel ?? "",
|
||||||
|
languages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (typeof question.upperLabel === "string") {
|
||||||
|
(clonedQuestion as TSurveyNPSQuestion).upperLabel = createI18nString(
|
||||||
|
question.upperLabel ?? "",
|
||||||
|
languages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ZSurveyNPSQuestion.parse(clonedQuestion);
|
||||||
|
|
||||||
|
case "rating":
|
||||||
|
if (typeof question.lowerLabel === "string") {
|
||||||
|
(clonedQuestion as TSurveyRatingQuestion).lowerLabel = createI18nString(
|
||||||
|
question.lowerLabel ?? "",
|
||||||
|
languages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof question.upperLabel === "string") {
|
||||||
|
(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 === "rating") {
|
};
|
||||||
(clonedQuestion as TSurveyRatingQuestion).lowerLabel = createI18nString(
|
|
||||||
question.lowerLabel ?? "",
|
export const extractLanguageIds = (languages: TLanguage[]): string[] => {
|
||||||
languages,
|
return languages.map((language) => language.id);
|
||||||
targetLanguageCode
|
|
||||||
);
|
|
||||||
(clonedQuestion as TSurveyRatingQuestion).upperLabel = createI18nString(
|
|
||||||
question.upperLabel ?? "",
|
|
||||||
languages,
|
|
||||||
targetLanguageCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return clonedQuestion;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// LGEGACY
|
// LGEGACY
|
||||||
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
// Helper function to maintain backwards compatibility for old survey objects before Multi Language
|
||||||
export const translateSurvey = (
|
export const translateSurvey = (
|
||||||
survey: TSurvey,
|
survey: Pick<TSurvey, "questions" | "welcomeCard" | "thankYouCard">,
|
||||||
surveyLanguages: TSurveyLanguage[],
|
surveyLanguages: TLanguage[]
|
||||||
targetLanguageCode?: string
|
): Pick<TSurvey, "questions" | "welcomeCard" | "thankYouCard"> => {
|
||||||
): TSurvey => {
|
const languages = extractLanguageIds(surveyLanguages);
|
||||||
const languages = extractLanguageCodes(surveyLanguages);
|
|
||||||
|
|
||||||
const translatedQuestions = survey.questions.map((question) => {
|
const translatedQuestions = survey.questions.map((question) => {
|
||||||
return translateQuestion(question, languages, targetLanguageCode);
|
return translateQuestion(question, languages);
|
||||||
});
|
});
|
||||||
const translatedWelcomeCard =
|
const translatedWelcomeCard = translateWelcomeCard(survey.welcomeCard, languages);
|
||||||
survey.welcomeCard && translateWelcomeCard(survey.welcomeCard, languages, targetLanguageCode);
|
const translatedThankYouCard = translateThankYouCard(survey.thankYouCard, languages);
|
||||||
const translatedThankYouCard =
|
|
||||||
survey.thankYouCard && translateThankYouCard(survey.thankYouCard, languages, targetLanguageCode);
|
|
||||||
const translatedSurvey = structuredClone(survey);
|
const translatedSurvey = structuredClone(survey);
|
||||||
return {
|
return {
|
||||||
...translatedSurvey,
|
...translatedSurvey,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
|||||||
import { displayCache } from "../display/cache";
|
import { displayCache } from "../display/cache";
|
||||||
import { getDisplaysByPersonId } from "../display/service";
|
import { getDisplaysByPersonId } from "../display/service";
|
||||||
import { reverseTranslateSurvey } from "../i18n/reverseTranslation";
|
import { reverseTranslateSurvey } from "../i18n/reverseTranslation";
|
||||||
import { translateSurvey } from "../i18n/utils";
|
|
||||||
import { personCache } from "../person/cache";
|
import { personCache } from "../person/cache";
|
||||||
import { getPerson } from "../person/service";
|
import { getPerson } from "../person/service";
|
||||||
import { productCache } from "../product/cache";
|
import { productCache } from "../product/cache";
|
||||||
@@ -366,30 +365,6 @@ export const transformToLegacySurvey = async (
|
|||||||
return formatDateFields(transformedSurvey, ZLegacySurvey);
|
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> => {
|
export const getSurveyCount = async (environmentId: string): Promise<number> => {
|
||||||
const count = await unstable_cache(
|
const count = await unstable_cache(
|
||||||
async () => {
|
async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user