+
{
+ 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);
}
}
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts
index 33ac95807a..a86c254c6d 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/templates/templates.ts
@@ -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,
diff --git a/apps/web/app/lib/questions.ts b/apps/web/app/lib/questions.ts
index 8738282a94..a38928af87 100644
--- a/apps/web/app/lib/questions.ts
+++ b/apps/web/app/lib/questions.ts
@@ -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",
},
},
{
diff --git a/packages/database/migrations/20240318050527_add_languages_and_survey_languages/data-migration-fix.ts b/packages/database/migrations/20240318050527_add_languages_and_survey_languages/data-migration-fix.ts
new file mode 100644
index 0000000000..7412163e9f
--- /dev/null
+++ b/packages/database/migrations/20240318050527_add_languages_and_survey_languages/data-migration-fix.ts
@@ -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());
diff --git a/packages/database/migrations/20240318050527_add_languages_and_survey_languages/lib/i18n.ts b/packages/database/migrations/20240318050527_add_languages_and_survey_languages/lib/i18n.ts
index 153c84a94c..c00b2b048f 100644
--- a/packages/database/migrations/20240318050527_add_languages_and_survey_languages/lib/i18n.ts
+++ b/packages/database/migrations/20240318050527_add_languages_and_survey_languages/lib/i18n.ts
@@ -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,
- surveyLanguages: TLanguage[]
+ languageCodes: string[]
): Pick => {
- 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;
+};
diff --git a/packages/database/package.json b/packages/database/package.json
index ecc976a73a..20156c023e 100644
--- a/packages/database/package.json
+++ b/packages/database/package.json
@@ -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",
diff --git a/packages/lib/i18n/i18n.mock.ts b/packages/lib/i18n/i18n.mock.ts
index b86f074d4b..fa7993fa25 100644
--- a/packages/lib/i18n/i18n.mock.ts
+++ b/packages/lib/i18n/i18n.mock.ts
@@ -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 = {
diff --git a/packages/lib/i18n/i18n.test.ts b/packages/lib/i18n/i18n.test.ts
index e9097bf8db..f96e2f2d88 100644
--- a/packages/lib/i18n/i18n.test.ts
+++ b/packages/lib/i18n/i18n.test.ts
@@ -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);
});
});
diff --git a/packages/lib/i18n/utils.ts b/packages/lib/i18n/utils.ts
index 70b397eddf..3ee77da0ae 100644
--- a/packages/lib/i18n/utils.ts
+++ b/packages/lib/i18n/utils.ts
@@ -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,
+ languageCodes: string[]
+): Pick => {
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,
diff --git a/packages/lib/survey/service.ts b/packages/lib/survey/service.ts
index 024d52baee..c76014af77 100644
--- a/packages/lib/survey/service.ts
+++ b/packages/lib/survey/service.ts
@@ -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 => {
- // 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 => {
const count = await unstable_cache(
async () => {
diff --git a/packages/types/LegacySurvey.ts b/packages/types/LegacySurvey.ts
index 5a3b8e3c99..189cf8e63f 100644
--- a/packages/types/LegacySurvey.ts
+++ b/packages/types/LegacySurvey.ts
@@ -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(),
});
diff --git a/packages/types/surveys.ts b/packages/types/surveys.ts
index bd1f6326d0..171cf5541f 100644
--- a/packages/types/surveys.ts
+++ b/packages/types/surveys.ts
@@ -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(),
});