feat: add browser language auto-selection

This commit is contained in:
Cursor Agent
2026-05-13 11:37:57 +00:00
parent 535c111860
commit dd58cadddb
28 changed files with 324 additions and 47 deletions
@@ -86,6 +86,7 @@ export const getEnvironmentStateData = async (environmentId: string): Promise<En
variables: true,
type: true,
showLanguageSwitch: true,
autoSelectLanguage: true,
languages: {
select: {
default: true,
+1
View File
@@ -4924,6 +4924,7 @@ export const previewSurvey = (projectName: string, t: TFunction): TSurvey => {
languages: [],
triggers: [],
showLanguageSwitch: false,
autoSelectLanguage: false,
followUps: [],
isBackButtonHidden: false,
isAutoProgressingEnabled: true,
@@ -226,6 +226,7 @@ const baseSurveyProperties = {
},
isVerifyEmailEnabled: false,
isSingleResponsePerEmailEnabled: false,
autoSelectLanguage: null,
attributeFilters: [],
...commonMockProperties,
};
+1
View File
@@ -58,6 +58,7 @@ export const selectSurvey = {
singleUse: true,
pin: true,
showLanguageSwitch: true,
autoSelectLanguage: true,
recaptcha: true,
metadata: true,
customHeadScripts: true,
+21 -1
View File
@@ -2,7 +2,7 @@ import * as nextHeaders from "next/headers";
import { describe, expect, test, vi } from "vitest";
import { AVAILABLE_LOCALES, DEFAULT_LOCALE } from "@/lib/constants";
import { appLanguages } from "@/lib/i18n/utils";
import { findMatchingLocale } from "./locale";
import { findMatchingBrowserLanguageCodes, findMatchingLocale } from "./locale";
// Mock the Next.js headers function
vi.mock("next/headers", () => ({
@@ -36,6 +36,26 @@ describe("locale", () => {
expect(nextHeaders.headers).toHaveBeenCalled();
});
test("ignores Accept-Language quality values when matching locales", async () => {
vi.mocked(nextHeaders.headers).mockReturnValue({
get: vi.fn().mockReturnValue("de-DE;q=0.9,en-US;q=0.8"),
} as any);
const result = await findMatchingLocale();
expect(result).toBe("de-DE");
});
test("returns browser language codes without quality values", async () => {
vi.mocked(nextHeaders.headers).mockReturnValue({
get: vi.fn().mockReturnValue("es-MX,es;q=0.9,en-US;q=0.8"),
} as any);
const result = await findMatchingBrowserLanguageCodes();
expect(result).toEqual(["es-MX", "es", "en-US"]);
});
test("returns normalized match when available", async () => {
// Assuming we have 'en-US' in AVAILABLE_LOCALES but not 'en-GB'
const availableLocale = AVAILABLE_LOCALES.find((locale) => locale.startsWith("en-"));
+16 -2
View File
@@ -2,11 +2,25 @@ import { headers } from "next/headers";
import { TUserLocale } from "@formbricks/types/user";
import { AVAILABLE_LOCALES, DEFAULT_LOCALE } from "@/lib/constants";
const getAcceptedLanguageCodesFromHeader = (acceptLanguage: string | null): string[] => {
return (
acceptLanguage
?.split(",")
.map((language) => language.trim().split(";")[0].trim())
.filter(Boolean) ?? []
);
};
export const findMatchingBrowserLanguageCodes = async (): Promise<string[]> => {
const headersList = await headers();
return getAcceptedLanguageCodesFromHeader(headersList.get("accept-language"));
};
export const findMatchingLocale = async (): Promise<TUserLocale> => {
const headersList = await headers();
const acceptLanguage = headersList.get("accept-language");
const userLocales = acceptLanguage?.split(",");
if (!userLocales) {
const userLocales = getAcceptedLanguageCodesFromHeader(acceptLanguage);
if (!userLocales.length) {
return DEFAULT_LOCALE;
}
// First, try to find an exact match without normalization
+2
View File
@@ -1370,6 +1370,8 @@
"auto_save_disabled": "Auto-save disabled",
"auto_save_disabled_tooltip": "Your survey is only auto-saved when in draft. This assures public surveys are not unintentionally updated.",
"auto_save_on": "Auto-save on",
"auto_select_browser_language": "Auto-select browser language",
"auto_select_browser_language_description": "Use the respondent's browser language when the survey has a matching active language. Falls back to the default language.",
"automatically_close_survey_after_n_seconds_if_no_response": "Automatically close survey after <autoCloseInput /> seconds after trigger if no response.",
"automatically_close_the_survey_after_a_certain_number_of_responses": "Automatically close the survey after a certain number of responses.",
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Automatically close the survey if the user does not respond after certain number of seconds.",
@@ -50,6 +50,7 @@ export const ZSurveyInput = ZSurveyWithoutQuestionType.pick({
styling: true,
projectOverwrites: true,
showLanguageSwitch: true,
autoSelectLanguage: true,
})
.partial({
redirectUrl: true,
@@ -63,6 +64,7 @@ export const ZSurveyInput = ZSurveyWithoutQuestionType.pick({
styling: true,
projectOverwrites: true,
showLanguageSwitch: true,
autoSelectLanguage: true,
inlineTriggers: true,
displayPercentage: true,
})
+1
View File
@@ -39,6 +39,7 @@ export const selectSurvey = {
singleUse: true,
pin: true,
showLanguageSwitch: true,
autoSelectLanguage: true,
recaptcha: true,
isBackButtonHidden: true,
isAutoProgressingEnabled: true,
@@ -19,6 +19,7 @@ import { SurveyInactive } from "@/modules/survey/link/components/survey-inactive
import { VerifyEmail } from "@/modules/survey/link/components/verify-email";
import { TEnvironmentContextForLinkSurvey } from "@/modules/survey/link/lib/environment";
import { getEmailVerificationDetails } from "@/modules/survey/link/lib/helper";
import { getSurveyLanguageCode } from "@/modules/survey/link/lib/utils";
interface SurveyRendererProps {
survey: TSurvey;
@@ -36,6 +37,7 @@ interface SurveyRendererProps {
// New props - pre-fetched in parent
environmentContext: TEnvironmentContextForLinkSurvey;
locale: TUserLocale;
browserLanguageCodes?: string[];
responseCount?: number;
}
@@ -59,6 +61,7 @@ export const renderSurvey = async ({
isPreview,
environmentContext,
locale,
browserLanguageCodes = [],
responseCount,
}: SurveyRendererProps) => {
const langParam = searchParams.lang;
@@ -108,7 +111,7 @@ export const renderSurvey = async ({
<VerifyEmail
survey={survey}
isErrorComponent={true}
languageCode={getLanguageCode(langParam, survey)}
languageCode={getSurveyLanguageCode(langParam, survey, browserLanguageCodes)}
styling={project.styling}
locale={locale}
/>
@@ -118,7 +121,7 @@ export const renderSurvey = async ({
<VerifyEmail
singleUseId={searchParams.suId ?? ""}
survey={survey}
languageCode={getLanguageCode(langParam, survey)}
languageCode={getSurveyLanguageCode(langParam, survey, browserLanguageCodes)}
styling={project.styling}
locale={locale}
/>
@@ -127,7 +130,7 @@ export const renderSurvey = async ({
// Compute final styling based on project and survey settings
const styling = computeStyling(project.styling, survey.styling);
const languageCode = getLanguageCode(langParam, survey);
const languageCode = getSurveyLanguageCode(langParam, survey, browserLanguageCodes);
const publicDomain = getPublicDomain();
// Handle PIN-protected surveys
@@ -194,24 +197,3 @@ function computeStyling(
}
return surveyStyling?.overwriteThemeStyling ? surveyStyling : projectStyling;
}
/**
* Determines the language code to use for the survey.
* Checks URL parameter against available survey languages and returns
* "default" if language is not found or disabled.
*/
function getLanguageCode(langParam: string | undefined, survey: TSurvey): string {
if (!langParam) return "default";
const selectedLanguage = survey.languages.find((surveyLanguage) => {
return (
surveyLanguage.language.code.toLowerCase() === langParam.toLowerCase() ||
surveyLanguage.language.alias?.toLowerCase() === langParam.toLowerCase()
);
});
if (!selectedLanguage || selectedLanguage?.default || !selectedLanguage?.enabled) {
return "default";
}
return selectedLanguage.language.code;
}
@@ -1,6 +1,6 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { findMatchingLocale } from "@/lib/utils/locale";
import { findMatchingBrowserLanguageCodes, findMatchingLocale } from "@/lib/utils/locale";
import { getTranslate } from "@/lingodotdev/server";
import { verifyContactSurveyToken } from "@/modules/ee/contacts/lib/contact-survey-link";
import { getResponseCountBySurveyId } from "@/modules/survey/lib/response";
@@ -137,9 +137,10 @@ export const ContactSurveyPage = async (props: ContactSurveyPageProps) => {
}
// Parallel fetch of environment context and locale
const [environmentContext, locale, singleUseResponse] = await Promise.all([
const [environmentContext, locale, browserLanguageCodes, singleUseResponse] = await Promise.all([
getEnvironmentContextForLinkSurvey(survey.environmentId),
findMatchingLocale(),
findMatchingBrowserLanguageCodes(),
// Fetch existing response for this contact
getExistingContactResponse(survey.id, contactId)(),
]);
@@ -158,6 +159,7 @@ export const ContactSurveyPage = async (props: ContactSurveyPageProps) => {
singleUseResponse,
environmentContext,
locale,
browserLanguageCodes,
responseCount,
});
};
+1
View File
@@ -58,6 +58,7 @@ export const getSurveyWithMetadata = reactCache(async (surveyId: string) => {
styling: true,
surveyClosedMessage: true,
showLanguageSwitch: true,
autoSelectLanguage: true,
recaptcha: true,
metadata: true,
+60 -1
View File
@@ -3,7 +3,13 @@ import { TJsEnvironmentStateSurvey } from "@formbricks/types/js";
import { TSurveyBlock } from "@formbricks/types/surveys/blocks";
import { TSurveyElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { getElementsFromSurveyBlocks, getWebAppLocale, isRTL, isRTLLanguage } from "./utils";
import {
getElementsFromSurveyBlocks,
getSurveyLanguageCode,
getWebAppLocale,
isRTL,
isRTLLanguage,
} from "./utils";
const createMockSurvey = (languages: TSurvey["languages"] = []): TSurvey =>
({
@@ -45,6 +51,7 @@ const createMockSurvey = (languages: TSurvey["languages"] = []): TSurvey =>
delay: 0,
autoComplete: null,
showLanguageSwitch: null,
autoSelectLanguage: null,
recaptcha: null,
isBackButtonHidden: false,
isCaptureIpEnabled: false,
@@ -98,6 +105,58 @@ describe("getWebAppLocale", () => {
});
});
describe("getSurveyLanguageCode", () => {
const language = (code: string, overrides: Partial<TSurvey["languages"][number]> = {}) => ({
language: {
id: `lang-${code}`,
code,
alias: null,
createdAt: new Date(),
updatedAt: new Date(),
projectId: "p1",
},
default: false,
enabled: true,
...overrides,
});
test("uses the URL language parameter before browser language auto-selection", () => {
const survey = {
...createMockSurvey([language("en", { default: true }), language("de")]),
autoSelectLanguage: true,
};
expect(getSurveyLanguageCode("de", survey, ["en-US"])).toBe("de");
});
test("matches browser language exactly when auto-selection is enabled", () => {
const survey = {
...createMockSurvey([language("en", { default: true }), language("de-DE")]),
autoSelectLanguage: true,
};
expect(getSurveyLanguageCode(undefined, survey, ["de-DE", "en-US"])).toBe("de-DE");
});
test("matches browser language by base language when exact variant is unavailable", () => {
const survey = {
...createMockSurvey([language("en", { default: true }), language("es-ES")]),
autoSelectLanguage: true,
};
expect(getSurveyLanguageCode(undefined, survey, ["es-MX", "en-US"])).toBe("es-ES");
});
test("falls back to default language when auto-selection is disabled or unmatched", () => {
const survey = createMockSurvey([language("en", { default: true }), language("de")]);
expect(getSurveyLanguageCode(undefined, survey, ["de-DE"])).toBe("default");
expect(getSurveyLanguageCode(undefined, { ...survey, autoSelectLanguage: true }, ["fr-FR"])).toBe(
"default"
);
});
});
describe("isRTL", () => {
test("detects RTL characters", () => {
expect(isRTL("مرحبا")).toBe(true);
+73
View File
@@ -55,6 +55,79 @@ export function isRTLLanguage(survey: TJsEnvironmentStateSurvey, languageCode: s
export const getElementsFromSurveyBlocks = (blocks: TSurveyBlock[]): TSurveyElement[] =>
blocks.flatMap((block) => block.elements);
const normalizeLanguageCode = (languageCode: string): string =>
languageCode.trim().split(";")[0].replace("_", "-").toLowerCase();
const getBaseLanguageCode = (languageCode: string): string =>
normalizeLanguageCode(languageCode).split("-")[0];
const getSelectableLanguageCode = (surveyLanguage: TSurvey["languages"][number]): string | undefined => {
if (surveyLanguage.default) return "default";
if (!surveyLanguage.enabled) return undefined;
return surveyLanguage.language.code;
};
const findExactLanguageMatch = (survey: TSurvey, languageCode: string): string | undefined => {
const normalizedLanguageCode = normalizeLanguageCode(languageCode);
const selectedLanguage = survey.languages.find((surveyLanguage) => {
return (
normalizeLanguageCode(surveyLanguage.language.code) === normalizedLanguageCode ||
(surveyLanguage.language.alias
? normalizeLanguageCode(surveyLanguage.language.alias) === normalizedLanguageCode
: false)
);
});
return selectedLanguage ? getSelectableLanguageCode(selectedLanguage) : undefined;
};
const findLooseLanguageMatch = (survey: TSurvey, languageCode: string): string | undefined => {
const baseLanguageCode = getBaseLanguageCode(languageCode);
for (const surveyLanguage of survey.languages) {
const selectableLanguageCode = getSelectableLanguageCode(surveyLanguage);
if (!selectableLanguageCode) continue;
const languageBaseCode = getBaseLanguageCode(surveyLanguage.language.code);
const aliasBaseCode = surveyLanguage.language.alias
? getBaseLanguageCode(surveyLanguage.language.alias)
: undefined;
if (languageBaseCode === baseLanguageCode || aliasBaseCode === baseLanguageCode) {
return selectableLanguageCode;
}
}
return undefined;
};
export const getSurveyLanguageCode = (
langParam: string | undefined,
survey: TSurvey,
browserLanguageCodes: string[] = []
): string => {
if (langParam) {
return findExactLanguageMatch(survey, langParam) ?? "default";
}
if (!survey.autoSelectLanguage) {
return "default";
}
for (const browserLanguageCode of browserLanguageCodes) {
const exactMatch = findExactLanguageMatch(survey, browserLanguageCode);
if (exactMatch) return exactMatch;
}
for (const browserLanguageCode of browserLanguageCodes) {
const looseMatch = findLooseLanguageMatch(survey, browserLanguageCode);
if (looseMatch) return looseMatch;
}
return "default";
};
/**
* Maps survey language codes to web app locale codes.
* Falls back to "en-US" if the language is not available in web app locales.
+4 -2
View File
@@ -3,7 +3,7 @@ import { notFound } from "next/navigation";
import { logger } from "@formbricks/logger";
import { ZId } from "@formbricks/types/common";
import { TSurvey } from "@formbricks/types/surveys/types";
import { findMatchingLocale } from "@/lib/utils/locale";
import { findMatchingBrowserLanguageCodes, findMatchingLocale } from "@/lib/utils/locale";
import { getResponseCountBySurveyId } from "@/modules/survey/lib/response";
import { SurveyInactive } from "@/modules/survey/link/components/survey-inactive";
import { renderSurvey } from "@/modules/survey/link/components/survey-renderer";
@@ -101,9 +101,10 @@ export const LinkSurveyPage = async (props: LinkSurveyPageProps) => {
}
// Stage 2: Parallel fetch of all remaining data
const [environmentContext, locale, singleUseResponse] = await Promise.all([
const [environmentContext, locale, browserLanguageCodes, singleUseResponse] = await Promise.all([
getEnvironmentContextForLinkSurvey(survey.environmentId),
findMatchingLocale(),
findMatchingBrowserLanguageCodes(),
// Only fetch single-use response if we have a validated ID
isSingleUseSurvey && singleUseId
? getResponseBySingleUseId(survey.id, singleUseId)()
@@ -124,6 +125,7 @@ export const LinkSurveyPage = async (props: LinkSurveyPageProps) => {
isPreview,
environmentContext,
locale,
browserLanguageCodes,
responseCount,
});
};
@@ -232,6 +232,10 @@ export const LanguageView = ({
setLocalSurvey({ ...localSurvey, showLanguageSwitch: !localSurvey.showLanguageSwitch });
};
const handleAutoSelectLanguageToggle = () => {
setLocalSurvey({ ...localSurvey, autoSelectLanguage: !localSurvey.autoSelectLanguage });
};
const openTranslationModal = (code: string) => {
setActiveLanguageCode(code);
setTranslationModalOpen(true);
@@ -456,6 +460,16 @@ export const LanguageView = ({
)}
childBorder={true}
/>
<AdvancedOptionToggle
customContainerClass="px-0 pt-0"
htmlId="autoSelectLanguage"
disabled={enabledLanguages.length <= 1}
isChecked={!!localSurvey.autoSelectLanguage}
onToggle={handleAutoSelectLanguageToggle}
title={t("environments.surveys.edit.auto_select_browser_language")}
description={t("environments.surveys.edit.auto_select_browser_language_description")}
childBorder={true}
/>
</div>
)}
@@ -36,6 +36,7 @@ export const getMinimalSurvey = (t: TFunction): TSurvey => ({
segment: null,
languages: [],
showLanguageSwitch: false,
autoSelectLanguage: false,
isVerifyEmailEnabled: false,
isSingleResponsePerEmailEnabled: false,
variables: [],
@@ -0,0 +1 @@
ALTER TABLE "Survey" ADD COLUMN "autoSelectLanguage" BOOLEAN;
+1
View File
@@ -401,6 +401,7 @@ model Survey {
displayPercentage Decimal?
languages SurveyLanguage[]
showLanguageSwitch Boolean?
autoSelectLanguage Boolean?
followUps SurveyFollowUp[]
/// [SurveyRecaptcha]
recaptcha Json? @default("{\"enabled\": false, \"threshold\":0.1}")
+4
View File
@@ -55,6 +55,10 @@ const ZSurveyBase = z.object({
status: z.enum(SurveyStatus).describe("The status of the survey"),
thankYouMessage: z.string().nullable().describe("The thank you message of the survey"),
showLanguageSwitch: z.boolean().nullable().describe("Whether to show the language switch"),
autoSelectLanguage: z
.boolean()
.nullable()
.describe("Whether to automatically select the survey language from the respondent's browser"),
showThankYouMessage: z.boolean().nullable().describe("Whether to show the thank you message"),
welcomeCard: z
.object({
@@ -22,6 +22,7 @@ export const mockConfig: TConfig = {
variables: [],
type: "app", // "link" or "app"
showLanguageSwitch: true,
autoSelectLanguage: false,
endings: [],
autoClose: 5,
status: "inProgress", // whatever statuses you use
@@ -482,6 +482,30 @@ describe("utils.ts", () => {
expect(getLanguageCode(survey, "fr")).toBe("fr");
expect(getLanguageCode(survey, "fr-FR")).toBe("fr");
});
test("returns a loose variant match for the selected language", () => {
const survey = {
languages: [
{ language: { code: "en" }, default: true, enabled: true },
{ language: { code: "es-ES" }, default: false, enabled: true },
],
} as unknown as TEnvironmentStateSurvey;
expect(getLanguageCode(survey, "es-MX")).toBe("es-ES");
});
test("uses fallback languages only when auto-select is enabled", () => {
const survey = {
autoSelectLanguage: true,
languages: [
{ language: { code: "en" }, default: true, enabled: true },
{ language: { code: "de" }, default: false, enabled: true },
],
} as unknown as TEnvironmentStateSurvey;
expect(getLanguageCode(survey, undefined, ["de-DE", "en-US"])).toBe("de");
expect(getLanguageCode({ ...survey, autoSelectLanguage: false }, undefined, ["de-DE"])).toBe("default");
});
});
// ---------------------------------------------------------------------------------
+66 -14
View File
@@ -176,27 +176,79 @@ export const getDefaultLanguageCode = (survey: TEnvironmentStateSurvey): string
if (defaultSurveyLanguage) return defaultSurveyLanguage.language.code;
};
export const getLanguageCode = (survey: TEnvironmentStateSurvey, language?: string): string | undefined => {
const availableLanguageCodes = survey.languages.map((surveyLanguage) => surveyLanguage.language.code);
if (!language) return "default";
const normalizeLanguageCode = (languageCode: string): string =>
languageCode.trim().split(";")[0].replace("_", "-").toLowerCase();
const getBaseLanguageCode = (languageCode: string): string =>
normalizeLanguageCode(languageCode).split("-")[0];
const getSelectableLanguageCode = (surveyLanguage: TEnvironmentStateSurvey["languages"][number]) => {
if (surveyLanguage.default) {
return "default";
}
if (!surveyLanguage.enabled) {
return undefined;
}
return surveyLanguage.language.code;
};
const findExactLanguageMatch = (survey: TEnvironmentStateSurvey, language: string): string | undefined => {
const normalizedLanguageCode = normalizeLanguageCode(language);
const selectedLanguage = survey.languages.find((surveyLanguage) => {
return (
surveyLanguage.language.code.toLowerCase() === language.toLowerCase() ||
surveyLanguage.language.alias?.toLowerCase() === language.toLowerCase()
normalizeLanguageCode(surveyLanguage.language.code) === normalizedLanguageCode ||
(surveyLanguage.language.alias
? normalizeLanguageCode(surveyLanguage.language.alias) === normalizedLanguageCode
: false)
);
});
if (selectedLanguage?.default) {
return "default";
return selectedLanguage ? getSelectableLanguageCode(selectedLanguage) : undefined;
};
const findLooseLanguageMatch = (survey: TEnvironmentStateSurvey, language: string): string | undefined => {
const baseLanguageCode = getBaseLanguageCode(language);
for (const surveyLanguage of survey.languages) {
const selectableLanguageCode = getSelectableLanguageCode(surveyLanguage);
if (!selectableLanguageCode) continue;
const languageBaseCode = getBaseLanguageCode(surveyLanguage.language.code);
const aliasBaseCode = surveyLanguage.language.alias
? getBaseLanguageCode(surveyLanguage.language.alias)
: undefined;
if (languageBaseCode === baseLanguageCode || aliasBaseCode === baseLanguageCode) {
return selectableLanguageCode;
}
}
if (
!selectedLanguage ||
!selectedLanguage.enabled ||
!availableLanguageCodes.includes(selectedLanguage.language.code)
) {
return undefined;
return undefined;
};
export const getLanguageCode = (
survey: TEnvironmentStateSurvey,
language?: string,
fallbackLanguages: string[] = []
): string | undefined => {
if (language) {
return findExactLanguageMatch(survey, language) ?? findLooseLanguageMatch(survey, language);
}
return selectedLanguage.language.code;
if (!survey.autoSelectLanguage) return "default";
for (const fallbackLanguage of fallbackLanguages) {
const exactMatch = findExactLanguageMatch(survey, fallbackLanguage);
if (exactMatch) return exactMatch;
}
for (const fallbackLanguage of fallbackLanguages) {
const looseMatch = findLooseLanguageMatch(survey, fallbackLanguage);
if (looseMatch) return looseMatch;
}
return "default";
};
export const getSecureRandom = (): number => {
@@ -24,6 +24,7 @@ export const mockSurvey: TEnvironmentStateSurvey = {
},
type: "app", // "link" or "app"
showLanguageSwitch: true,
autoSelectLanguage: false,
endings: [],
autoClose: 5,
status: "inProgress", // whatever statuses you use
+14 -1
View File
@@ -18,6 +18,15 @@ import { type TTrackProperties } from "@/types/survey";
let isSurveyRunning = false;
const getBrowserLanguageCodes = (): string[] => {
if (typeof navigator === "undefined") return [];
return navigator.languages?.length
? [...navigator.languages]
: navigator.language
? [navigator.language]
: [];
};
export const setIsSurveyRunning = (value: boolean): void => {
isSurveyRunning = value;
};
@@ -91,7 +100,11 @@ export const renderWidget = async (
let languageCode = "default";
if (isMultiLanguageSurvey) {
const displayLanguage = getLanguageCode(survey, language);
const displayLanguage = getLanguageCode(
survey,
language,
survey.autoSelectLanguage ? getBrowserLanguageCodes() : []
);
//if survey is not available in selected language, survey wont be shown
if (!displayLanguage) {
logger.debug(`Survey "${survey.id}" is not available in specified language.`);
+1
View File
@@ -10,6 +10,7 @@ export type TEnvironmentStateSurvey = Pick<
| "variables"
| "type"
| "showLanguageSwitch"
| "autoSelectLanguage"
| "endings"
| "autoClose"
| "status"
+1
View File
@@ -15,6 +15,7 @@ export const ZJsEnvironmentStateSurvey = ZSurveyBase.pick({
variables: true,
type: true,
showLanguageSwitch: true,
autoSelectLanguage: true,
languages: true,
endings: true,
autoClose: true,
+1
View File
@@ -907,6 +907,7 @@ export const ZSurveyBase = z.object({
projectOverwrites: ZSurveyProjectOverwrites.nullable(),
styling: ZSurveyStyling.nullable(),
showLanguageSwitch: z.boolean().nullable(),
autoSelectLanguage: z.boolean().nullish(),
surveyClosedMessage: ZSurveyClosedMessage.nullable(),
segment: ZSegment.nullable(),
singleUse: ZSurveySingleUse.nullable(),