diff --git a/apps/web/lib/i18n/utils.ts b/apps/web/lib/i18n/utils.ts
index 6caa680823ad..b57ef16db913 100644
--- a/apps/web/lib/i18n/utils.ts
+++ b/apps/web/lib/i18n/utils.ts
@@ -130,217 +130,78 @@ export const appLanguages = [
code: "en-US",
label: {
"en-US": "English (US)",
- "de-DE": "Englisch (US)",
- "pt-BR": "Inglês (EUA)",
- "fr-FR": "Anglais (États-Unis)",
- "zh-Hant-TW": "英文 (美國)",
- "pt-PT": "Inglês (EUA)",
- "ro-RO": "Engleză (SUA)",
- "ja-JP": "英語(米国)",
- "zh-Hans-CN": "英语(美国)",
- "nl-NL": "Engels (VS)",
- "es-ES": "Inglés (EE.UU.)",
- "sv-SE": "Engelska (USA)",
- "ru-RU": "Английский (США)",
},
},
{
code: "de-DE",
label: {
"en-US": "German",
- "de-DE": "Deutsch",
- "pt-BR": "Alemão",
- "fr-FR": "Allemand",
- "zh-Hant-TW": "德語",
- "pt-PT": "Alemão",
- "ro-RO": "Germană",
- "ja-JP": "ドイツ語",
- "zh-Hans-CN": "德语",
- "nl-NL": "Duits",
- "es-ES": "Alemán",
- "sv-SE": "Tyska",
- "ru-RU": "Немецкий",
},
},
{
code: "pt-BR",
label: {
"en-US": "Portuguese (Brazil)",
- "de-DE": "Portugiesisch (Brasilien)",
- "pt-BR": "Português (Brasil)",
- "fr-FR": "Portugais (Brésil)",
- "zh-Hant-TW": "葡萄牙語 (巴西)",
- "pt-PT": "Português (Brasil)",
- "ro-RO": "Portugheză (Brazilia)",
- "ja-JP": "ポルトガル語(ブラジル)",
- "zh-Hans-CN": "葡萄牙语(巴西)",
- "nl-NL": "Portugees (Brazilië)",
- "es-ES": "Portugués (Brasil)",
- "sv-SE": "Portugisiska (Brasilien)",
- "ru-RU": "Португальский (Бразилия)",
},
},
{
code: "fr-FR",
label: {
"en-US": "French",
- "de-DE": "Französisch",
- "pt-BR": "Francês",
- "fr-FR": "Français",
- "zh-Hant-TW": "法語",
- "pt-PT": "Francês",
- "ro-RO": "Franceză",
- "ja-JP": "フランス語",
- "zh-Hans-CN": "法语",
- "nl-NL": "Frans",
- "es-ES": "Francés",
- "sv-SE": "Franska",
- "ru-RU": "Французский",
},
},
{
code: "zh-Hant-TW",
label: {
"en-US": "Chinese (Traditional)",
- "de-DE": "Chinesisch (Traditionell)",
- "pt-BR": "Chinês (Tradicional)",
- "fr-FR": "Chinois (Traditionnel)",
- "zh-Hant-TW": "繁體中文",
- "pt-PT": "Chinês (Tradicional)",
- "ro-RO": "Chineza (Tradițională)",
- "ja-JP": "中国語(繁体字)",
- "zh-Hans-CN": "繁体中文",
- "nl-NL": "Chinees (Traditioneel)",
- "es-ES": "Chino (Tradicional)",
- "sv-SE": "Kinesiska (traditionell)",
- "ru-RU": "Китайский (традиционный)",
},
},
{
code: "pt-PT",
label: {
"en-US": "Portuguese (Portugal)",
- "de-DE": "Portugiesisch (Portugal)",
- "pt-BR": "Português (Portugal)",
- "fr-FR": "Portugais (Portugal)",
- "zh-Hant-TW": "葡萄牙語 (葡萄牙)",
- "pt-PT": "Português (Portugal)",
- "ro-RO": "Portugheză (Portugalia)",
- "ja-JP": "ポルトガル語(ポルトガル)",
- "zh-Hans-CN": "葡萄牙语(葡萄牙)",
- "nl-NL": "Portugees (Portugal)",
- "es-ES": "Portugués (Portugal)",
- "sv-SE": "Portugisiska (Portugal)",
- "ru-RU": "Португальский (Португалия)",
},
},
{
code: "ro-RO",
label: {
"en-US": "Romanian",
- "de-DE": "Rumänisch",
- "pt-BR": "Romeno",
- "fr-FR": "Roumain",
- "zh-Hant-TW": "羅馬尼亞語",
- "pt-PT": "Romeno",
- "ro-RO": "Română",
- "ja-JP": "ルーマニア語",
- "zh-Hans-CN": "罗马尼亚语",
- "nl-NL": "Roemeens",
- "es-ES": "Rumano",
- "sv-SE": "Rumänska",
- "ru-RU": "Румынский",
},
},
{
code: "ja-JP",
label: {
"en-US": "Japanese",
- "de-DE": "Japanisch",
- "pt-BR": "Japonês",
- "fr-FR": "Japonais",
- "zh-Hant-TW": "日語",
- "pt-PT": "Japonês",
- "ro-RO": "Japoneză",
- "ja-JP": "日本語",
- "zh-Hans-CN": "日语",
- "nl-NL": "Japans",
- "es-ES": "Japonés",
- "sv-SE": "Japanska",
- "ru-RU": "Японский",
},
},
{
code: "zh-Hans-CN",
label: {
"en-US": "Chinese (Simplified)",
- "de-DE": "Chinesisch (Vereinfacht)",
- "pt-BR": "Chinês (Simplificado)",
- "fr-FR": "Chinois (Simplifié)",
- "zh-Hant-TW": "簡體中文",
- "pt-PT": "Chinês (Simplificado)",
- "ro-RO": "Chineza (Simplificată)",
- "ja-JP": "中国語(簡体字)",
- "zh-Hans-CN": "简体中文",
- "nl-NL": "Chinees (Vereenvoudigd)",
- "es-ES": "Chino (Simplificado)",
- "sv-SE": "Kinesiska (förenklad)",
- "ru-RU": "Китайский (упрощенный)",
},
},
{
code: "nl-NL",
label: {
"en-US": "Dutch",
- "de-DE": "Niederländisch",
- "pt-BR": "Holandês",
- "fr-FR": "Néerlandais",
- "zh-Hant-TW": "荷蘭語",
- "pt-PT": "Holandês",
- "ro-RO": "Olandeza",
- "ja-JP": "オランダ語",
- "zh-Hans-CN": "荷兰语",
- "nl-NL": "Nederlands",
- "es-ES": "Neerlandés",
- "sv-SE": "Nederländska",
- "ru-RU": "Голландский",
},
},
{
code: "es-ES",
label: {
"en-US": "Spanish",
- "de-DE": "Spanisch",
- "pt-BR": "Espanhol",
- "fr-FR": "Espagnol",
- "zh-Hant-TW": "西班牙語",
- "pt-PT": "Espanhol",
- "ro-RO": "Spaniol",
- "ja-JP": "スペイン語",
- "zh-Hans-CN": "西班牙语",
- "nl-NL": "Spaans",
- "es-ES": "Español",
- "sv-SE": "Spanska",
- "ru-RU": "Испанский",
},
},
{
code: "sv-SE",
label: {
"en-US": "Swedish",
- "de-DE": "Schwedisch",
- "pt-BR": "Sueco",
- "fr-FR": "Suédois",
- "zh-Hant-TW": "瑞典語",
- "pt-PT": "Sueco",
- "ro-RO": "Suedeză",
- "ja-JP": "スウェーデン語",
- "zh-Hans-CN": "瑞典语",
- "nl-NL": "Zweeds",
- "es-ES": "Sueco",
- "sv-SE": "Svenska",
- "ru-RU": "Шведский",
+ },
+ },
+ {
+ code: "ru-RU",
+ label: {
+ "en-US": "Russian",
},
},
];
-export { iso639Languages };
diff --git a/apps/web/lib/utils/locale.test.ts b/apps/web/lib/utils/locale.test.ts
index 6330d98f3fc1..07e2d5ecb62e 100644
--- a/apps/web/lib/utils/locale.test.ts
+++ b/apps/web/lib/utils/locale.test.ts
@@ -90,11 +90,10 @@ describe("locale", () => {
// Verify sv-SE is in AVAILABLE_LOCALES
expect(AVAILABLE_LOCALES).toContain("sv-SE");
- // Verify Swedish has a language entry with proper labels
+ // Verify Swedish has a language entry with proper label
const swedishLanguage = appLanguages.find((lang) => lang.code === "sv-SE");
expect(swedishLanguage).toBeDefined();
expect(swedishLanguage?.label["en-US"]).toBe("Swedish");
- expect(swedishLanguage?.label["sv-SE"]).toBe("Svenska");
// Verify the locale can be matched from Accept-Language header
vi.mocked(nextHeaders.headers).mockReturnValue({
diff --git a/docs/api-reference/openapi.json b/docs/api-reference/openapi.json
index eb26118ceabc..78c20f3ca933 100644
--- a/docs/api-reference/openapi.json
+++ b/docs/api-reference/openapi.json
@@ -5868,6 +5868,7 @@
"jpeg",
"jpg",
"webp",
+ "ico",
"pdf",
"eml",
"doc",
@@ -5883,6 +5884,7 @@
"avi",
"mkv",
"webm",
+ "mp3",
"zip",
"rar",
"7z",
diff --git a/packages/survey-ui/src/components/elements/consent.tsx b/packages/survey-ui/src/components/elements/consent.tsx
index c1d0fda00285..3667808ee0a9 100644
--- a/packages/survey-ui/src/components/elements/consent.tsx
+++ b/packages/survey-ui/src/components/elements/consent.tsx
@@ -24,6 +24,8 @@ export interface ConsentProps {
onChange: (checked: boolean) => void;
/** Whether the field is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Error message to display */
errorMessage?: string;
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto' (auto-detect from content) */
@@ -45,6 +47,7 @@ function Consent({
value = false,
onChange,
required = false,
+ requiredLabel,
errorMessage,
dir = "auto",
disabled = false,
@@ -63,6 +66,7 @@ function Consent({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
diff --git a/packages/survey-ui/src/components/elements/cta.tsx b/packages/survey-ui/src/components/elements/cta.tsx
index f4e915def11b..d07bb471278e 100644
--- a/packages/survey-ui/src/components/elements/cta.tsx
+++ b/packages/survey-ui/src/components/elements/cta.tsx
@@ -26,6 +26,8 @@ export interface CTAProps {
onClick: () => void;
/** Whether the field is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Error message to display */
errorMessage?: string;
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto' (auto-detect from content) */
@@ -50,6 +52,7 @@ function CTA({
buttonExternal = false,
onClick,
required = false,
+ requiredLabel,
errorMessage,
dir = "auto",
disabled = false,
@@ -73,6 +76,7 @@ function CTA({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
@@ -82,8 +86,7 @@ function CTA({
- {buttonExternal && (
-
+ {buttonExternal ?
-
- )}
+
: null}
);
diff --git a/packages/survey-ui/src/components/elements/date.tsx b/packages/survey-ui/src/components/elements/date.tsx
index d4d4ec2f168a..244fd29594f3 100644
--- a/packages/survey-ui/src/components/elements/date.tsx
+++ b/packages/survey-ui/src/components/elements/date.tsx
@@ -19,6 +19,8 @@ interface DateElementProps {
onChange: (value: string) => void;
/** Whether the field is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Minimum date allowed (ISO format: YYYY-MM-DD) */
minDate?: string;
/** Maximum date allowed (ISO format: YYYY-MM-DD) */
@@ -45,6 +47,7 @@ function DateElement({
value,
onChange,
required = false,
+ requiredLabel,
minDate,
maxDate,
dir = "auto",
@@ -152,6 +155,7 @@ function DateElement({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
diff --git a/packages/survey-ui/src/components/elements/file-upload.tsx b/packages/survey-ui/src/components/elements/file-upload.tsx
index 13f459fbf45e..5d3abeb5569b 100644
--- a/packages/survey-ui/src/components/elements/file-upload.tsx
+++ b/packages/survey-ui/src/components/elements/file-upload.tsx
@@ -37,6 +37,8 @@ interface FileUploadProps {
allowedFileExtensions?: string[];
/** Whether the field is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Error message to display */
errorMessage?: string;
/** Whether the component is in uploading state */
@@ -219,6 +221,7 @@ function FileUpload({
allowMultiple = false,
allowedFileExtensions,
required = false,
+ requiredLabel,
errorMessage,
isUploading = false,
dir = "auto",
@@ -279,6 +282,7 @@ function FileUpload({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
diff --git a/packages/survey-ui/src/components/elements/form-field.tsx b/packages/survey-ui/src/components/elements/form-field.tsx
index ad639e102a87..6af8653b64d7 100644
--- a/packages/survey-ui/src/components/elements/form-field.tsx
+++ b/packages/survey-ui/src/components/elements/form-field.tsx
@@ -37,6 +37,8 @@ interface FormFieldProps {
onChange: (value: Record) => void;
/** Whether the entire form is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Error message to display */
errorMessage?: string;
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto' (auto-detect from content) */
@@ -57,6 +59,7 @@ function FormField({
value = {},
onChange,
required = false,
+ requiredLabel,
errorMessage,
dir = "auto",
disabled = false,
@@ -103,6 +106,7 @@ function FormField({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
imageUrl={imageUrl}
videoUrl={videoUrl}
/>
diff --git a/packages/survey-ui/src/components/elements/matrix.tsx b/packages/survey-ui/src/components/elements/matrix.tsx
index 8ba763a851dc..11471fbb13a5 100644
--- a/packages/survey-ui/src/components/elements/matrix.tsx
+++ b/packages/survey-ui/src/components/elements/matrix.tsx
@@ -35,6 +35,8 @@ interface MatrixProps {
onChange: (value: Record) => void;
/** Whether the field is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Error message to display */
errorMessage?: string;
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto' (auto-detect from content) */
@@ -57,6 +59,7 @@ function Matrix({
value = {},
onChange,
required = false,
+ requiredLabel,
errorMessage,
dir = "auto",
disabled = false,
@@ -84,6 +87,7 @@ function Matrix({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
diff --git a/packages/survey-ui/src/components/elements/multi-select.tsx b/packages/survey-ui/src/components/elements/multi-select.tsx
index 5e40625fcd8e..c6278f939321 100644
--- a/packages/survey-ui/src/components/elements/multi-select.tsx
+++ b/packages/survey-ui/src/components/elements/multi-select.tsx
@@ -45,6 +45,8 @@ interface MultiSelectProps {
onChange: (value: string[]) => void;
/** Whether the field is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Error message to display below the options */
errorMessage?: string;
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto' (auto-detect from content) */
@@ -405,6 +407,7 @@ function MultiSelect({
value = [],
onChange,
required = false,
+ requiredLabel,
errorMessage,
dir = "auto",
disabled = false,
@@ -473,6 +476,7 @@ function MultiSelect({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
diff --git a/packages/survey-ui/src/components/elements/nps.tsx b/packages/survey-ui/src/components/elements/nps.tsx
index 3d822d0369a7..9684508887a8 100644
--- a/packages/survey-ui/src/components/elements/nps.tsx
+++ b/packages/survey-ui/src/components/elements/nps.tsx
@@ -25,6 +25,8 @@ interface NPSProps {
colorCoding?: boolean;
/** Whether the field is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Error message to display */
errorMessage?: string;
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto' (auto-detect from content) */
@@ -48,6 +50,7 @@ function NPS({
upperLabel,
colorCoding = false,
required = false,
+ requiredLabel,
errorMessage,
dir = "auto",
disabled = false,
@@ -171,6 +174,7 @@ function NPS({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
diff --git a/packages/survey-ui/src/components/elements/open-text.tsx b/packages/survey-ui/src/components/elements/open-text.tsx
index 3be9379ff78e..b3418878a1e0 100644
--- a/packages/survey-ui/src/components/elements/open-text.tsx
+++ b/packages/survey-ui/src/components/elements/open-text.tsx
@@ -14,6 +14,7 @@ interface OpenTextProps {
value?: string;
onChange: (value: string) => void;
required?: boolean;
+ requiredLabel?: string;
longAnswer?: boolean;
inputType?: "text" | "email" | "url" | "phone" | "number";
charLimit?: {
@@ -37,6 +38,7 @@ function OpenText({
inputId,
onChange,
required = false,
+ requiredLabel,
longAnswer = false,
inputType = "text",
charLimit,
@@ -72,6 +74,7 @@ function OpenText({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
diff --git a/packages/survey-ui/src/components/elements/picture-select.tsx b/packages/survey-ui/src/components/elements/picture-select.tsx
index 6d868034a9ab..3ed8b59d2322 100644
--- a/packages/survey-ui/src/components/elements/picture-select.tsx
+++ b/packages/survey-ui/src/components/elements/picture-select.tsx
@@ -37,6 +37,8 @@ interface PictureSelectProps {
allowMulti?: boolean;
/** Whether the field is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Error message to display */
errorMessage?: string;
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto' (auto-detect from content) */
@@ -59,6 +61,7 @@ function PictureSelect({
onChange,
allowMulti = false,
required = false,
+ requiredLabel,
errorMessage,
dir = "auto",
disabled = false,
@@ -96,6 +99,7 @@ function PictureSelect({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
diff --git a/packages/survey-ui/src/components/elements/ranking.tsx b/packages/survey-ui/src/components/elements/ranking.tsx
index 214125a850b6..04e03732d0ce 100644
--- a/packages/survey-ui/src/components/elements/ranking.tsx
+++ b/packages/survey-ui/src/components/elements/ranking.tsx
@@ -37,6 +37,8 @@ interface RankingProps {
onChange: (value: string[]) => void;
/** Whether the field is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Error message to display */
errorMessage?: string;
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto' (auto-detect from content) */
@@ -191,6 +193,7 @@ function Ranking({
value = [],
onChange,
required = false,
+ requiredLabel,
errorMessage,
dir = "auto",
disabled = false,
@@ -249,6 +252,7 @@ function Ranking({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
diff --git a/packages/survey-ui/src/components/elements/rating.tsx b/packages/survey-ui/src/components/elements/rating.tsx
index d0bfa224333e..95a50342b36b 100644
--- a/packages/survey-ui/src/components/elements/rating.tsx
+++ b/packages/survey-ui/src/components/elements/rating.tsx
@@ -137,6 +137,8 @@ interface RatingProps {
colorCoding?: boolean;
/** Whether the field is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Error message to display */
errorMessage?: string;
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto' (auto-detect from content) */
@@ -162,6 +164,7 @@ function Rating({
upperLabel,
colorCoding = false,
required = false,
+ requiredLabel,
errorMessage,
dir = "auto",
disabled = false,
@@ -406,6 +409,7 @@ function Rating({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
diff --git a/packages/survey-ui/src/components/elements/single-select.tsx b/packages/survey-ui/src/components/elements/single-select.tsx
index 61a4a5a40d5e..72bdb0203179 100644
--- a/packages/survey-ui/src/components/elements/single-select.tsx
+++ b/packages/survey-ui/src/components/elements/single-select.tsx
@@ -41,6 +41,8 @@ interface SingleSelectProps {
onChange: (value: string) => void;
/** Whether the field is required (shows asterisk indicator) */
required?: boolean;
+ /** Custom label for the required indicator */
+ requiredLabel?: string;
/** Error message to display below the options */
errorMessage?: string;
/** Text direction: 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto' (auto-detect from content) */
@@ -76,6 +78,7 @@ function SingleSelect({
value,
onChange,
required = false,
+ requiredLabel,
errorMessage,
dir = "auto",
disabled = false,
@@ -141,6 +144,7 @@ function SingleSelect({
headline={headline}
description={description}
required={required}
+ requiredLabel={requiredLabel}
htmlFor={inputId}
imageUrl={imageUrl}
videoUrl={videoUrl}
diff --git a/packages/survey-ui/src/components/general/element-header.tsx b/packages/survey-ui/src/components/general/element-header.tsx
index 3e38dcb96003..d94cb7a2ea35 100644
--- a/packages/survey-ui/src/components/general/element-header.tsx
+++ b/packages/survey-ui/src/components/general/element-header.tsx
@@ -8,6 +8,8 @@ interface ElementHeaderProps extends React.ComponentProps<"div"> {
headline: string;
description?: string;
required?: boolean;
+ /** Custom label for the required indicator. Defaults to "Required" */
+ requiredLabel?: string;
htmlFor?: string;
imageUrl?: string;
videoUrl?: string;
@@ -43,6 +45,7 @@ function ElementHeader({
headline,
description,
required = false,
+ requiredLabel = "Required",
htmlFor,
className,
imageUrl,
@@ -73,7 +76,7 @@ function ElementHeader({
{/* Headline */}
- {required ? Required : null}
+ {required ? {requiredLabel} : null}
{isHeadlineHtml && safeHeadlineHtml ? (
diff --git a/packages/surveys/src/components/elements/address-element.tsx b/packages/surveys/src/components/elements/address-element.tsx
index 453a6ed45aa5..bd00026d4fca 100644
--- a/packages/surveys/src/components/elements/address-element.tsx
+++ b/packages/surveys/src/components/elements/address-element.tsx
@@ -1,4 +1,5 @@
import { useState } from "preact/hooks";
+import { useTranslation } from "react-i18next";
import { FormField, type FormFieldConfig } from "@formbricks/survey-ui";
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
import type { TSurveyAddressElement } from "@formbricks/types/surveys/elements";
@@ -29,6 +30,7 @@ export function AddressElement({
}: Readonly
) {
const [startTime, setStartTime] = useState(performance.now());
const isCurrent = element.id === currentElementId;
+ const { t } = useTranslation();
useTtc(element.id, ttc, setTtc, startTime, setStartTime, isCurrent);
@@ -118,6 +120,7 @@ export function AddressElement({
value={convertToValueObject(value)}
onChange={handleChange}
required={element.required}
+ requiredLabel={t("common.required")}
dir={dir}
imageUrl={element.imageUrl}
videoUrl={element.videoUrl}
diff --git a/packages/surveys/src/components/elements/consent-element.tsx b/packages/surveys/src/components/elements/consent-element.tsx
index b7f51998b8f8..f484f6e6b9ac 100644
--- a/packages/surveys/src/components/elements/consent-element.tsx
+++ b/packages/surveys/src/components/elements/consent-element.tsx
@@ -66,6 +66,7 @@ export function ConsentElement({
value={value === "accepted"}
onChange={handleChange}
required={element.required}
+ requiredLabel={t("common.required")}
errorMessage={errorMessage}
dir={dir}
imageUrl={element.imageUrl}
diff --git a/packages/surveys/src/components/elements/contact-info-element.tsx b/packages/surveys/src/components/elements/contact-info-element.tsx
index cad44e46da5a..f9f0f4469c3e 100644
--- a/packages/surveys/src/components/elements/contact-info-element.tsx
+++ b/packages/surveys/src/components/elements/contact-info-element.tsx
@@ -1,4 +1,5 @@
import { useState } from "preact/hooks";
+import { useTranslation } from "react-i18next";
import { FormField, type FormFieldConfig } from "@formbricks/survey-ui";
import { type TResponseData, type TResponseTtc } from "@formbricks/types/responses";
import type { TSurveyContactInfoElement } from "@formbricks/types/surveys/elements";
@@ -30,6 +31,7 @@ export function ContactInfoElement({
}: Readonly) {
const [startTime, setStartTime] = useState(performance.now());
const isCurrent = element.id === currentElementId;
+ const { t } = useTranslation();
useTtc(element.id, ttc, setTtc, startTime, setStartTime, isCurrent);
@@ -114,6 +116,7 @@ export function ContactInfoElement({
value={convertToValueObject(value)}
onChange={handleChange}
required={element.required}
+ requiredLabel={t("common.required")}
dir={dir}
imageUrl={element.imageUrl}
videoUrl={element.videoUrl}
diff --git a/packages/surveys/src/components/elements/date-element.tsx b/packages/surveys/src/components/elements/date-element.tsx
index 2f987a7a4bd2..2add00c0d53c 100644
--- a/packages/surveys/src/components/elements/date-element.tsx
+++ b/packages/surveys/src/components/elements/date-element.tsx
@@ -76,6 +76,7 @@ export function DateElement({
minDate={getMinDate()}
maxDate={getMaxDate()}
required={element.required}
+ requiredLabel={t("common.required")}
errorMessage={errorMessage}
locale={languageCode}
imageUrl={element.imageUrl}
diff --git a/packages/surveys/src/components/elements/file-upload-element.tsx b/packages/surveys/src/components/elements/file-upload-element.tsx
index 6abb927c9479..6095e9d58572 100644
--- a/packages/surveys/src/components/elements/file-upload-element.tsx
+++ b/packages/surveys/src/components/elements/file-upload-element.tsx
@@ -354,6 +354,7 @@ export function FileUploadElement({
allowMultiple={element.allowMultipleFiles}
allowedFileExtensions={element.allowedFileExtensions}
required={element.required}
+ requiredLabel={t("common.required")}
errorMessage={errorMessage}
isUploading={isUploading}
imageUrl={element.imageUrl}
diff --git a/packages/surveys/src/components/elements/matrix-element.tsx b/packages/surveys/src/components/elements/matrix-element.tsx
index 2c6e94f6abaa..572217c605ae 100644
--- a/packages/surveys/src/components/elements/matrix-element.tsx
+++ b/packages/surveys/src/components/elements/matrix-element.tsx
@@ -151,6 +151,7 @@ export function MatrixElement({
value={convertValueToIds(value)}
onChange={handleChange}
required={element.required}
+ requiredLabel={t("common.required")}
errorMessage={errorMessage}
imageUrl={element.imageUrl}
videoUrl={element.videoUrl}
diff --git a/packages/surveys/src/components/elements/multiple-choice-multi-element.tsx b/packages/surveys/src/components/elements/multiple-choice-multi-element.tsx
index 4dff6ed99cb2..322753c2e5a9 100644
--- a/packages/surveys/src/components/elements/multiple-choice-multi-element.tsx
+++ b/packages/surveys/src/components/elements/multiple-choice-multi-element.tsx
@@ -263,6 +263,7 @@ export function MultipleChoiceMultiElement({
value={selectedValues}
onChange={handleMultiSelectChange}
required={element.required}
+ requiredLabel={t("common.required")}
errorMessage={errorMessage}
dir={dir}
otherOptionId={otherOption?.id}
diff --git a/packages/surveys/src/components/elements/multiple-choice-single-element.tsx b/packages/surveys/src/components/elements/multiple-choice-single-element.tsx
index 7783c1d6fd91..4adf7480bab6 100644
--- a/packages/surveys/src/components/elements/multiple-choice-single-element.tsx
+++ b/packages/surveys/src/components/elements/multiple-choice-single-element.tsx
@@ -185,6 +185,7 @@ export function MultipleChoiceSingleElement({
value={selectedValue}
onChange={handleChange}
required={element.required}
+ requiredLabel={t("common.required")}
errorMessage={errorMessage}
dir={dir}
otherOptionId={otherOption?.id}
diff --git a/packages/surveys/src/components/elements/nps-element.tsx b/packages/surveys/src/components/elements/nps-element.tsx
index 078920b051c5..70834403b43b 100644
--- a/packages/surveys/src/components/elements/nps-element.tsx
+++ b/packages/surveys/src/components/elements/nps-element.tsx
@@ -71,6 +71,7 @@ export function NPSElement({
upperLabel={getLocalizedValue(element.upperLabel, languageCode)}
colorCoding={element.isColorCodingEnabled}
required={element.required}
+ requiredLabel={t("common.required")}
errorMessage={errorMessage}
dir={dir}
imageUrl={element.imageUrl}
diff --git a/packages/surveys/src/components/elements/open-text-element.tsx b/packages/surveys/src/components/elements/open-text-element.tsx
index 013ab9bc56a8..f15a0f3c45c1 100644
--- a/packages/surveys/src/components/elements/open-text-element.tsx
+++ b/packages/surveys/src/components/elements/open-text-element.tsx
@@ -123,6 +123,7 @@ export function OpenTextElement({
value={value}
onChange={handleChange}
required={element.required}
+ requiredLabel={t("common.required")}
longAnswer={element.longAnswer !== false}
inputType={getInputType()}
charLimit={element.inputType === "text" ? element.charLimit : undefined}
diff --git a/packages/surveys/src/components/elements/picture-selection-element.tsx b/packages/surveys/src/components/elements/picture-selection-element.tsx
index 6d1ba1d0da44..f75b36ede99f 100644
--- a/packages/surveys/src/components/elements/picture-selection-element.tsx
+++ b/packages/surveys/src/components/elements/picture-selection-element.tsx
@@ -95,6 +95,7 @@ export function PictureSelectionElement({
onChange={handleChange}
allowMulti={element.allowMulti}
required={element.required}
+ requiredLabel={t("common.required")}
dir={dir}
errorMessage={errorMessage}
imageUrl={element.imageUrl}
diff --git a/packages/surveys/src/components/elements/ranking-element.tsx b/packages/surveys/src/components/elements/ranking-element.tsx
index f984e77a84e8..6ef3ca00e587 100644
--- a/packages/surveys/src/components/elements/ranking-element.tsx
+++ b/packages/surveys/src/components/elements/ranking-element.tsx
@@ -136,6 +136,7 @@ export function RankingElement({
value={selectedValues}
onChange={handleChange}
required={element.required}
+ requiredLabel={t("common.required")}
errorMessage={errorMessage}
imageUrl={element.imageUrl}
videoUrl={element.videoUrl}
diff --git a/packages/surveys/src/components/elements/rating-element.tsx b/packages/surveys/src/components/elements/rating-element.tsx
index e2fe9633d37b..aca89a4efec1 100644
--- a/packages/surveys/src/components/elements/rating-element.tsx
+++ b/packages/surveys/src/components/elements/rating-element.tsx
@@ -70,6 +70,7 @@ export function RatingElement({
upperLabel={getLocalizedValue(element.upperLabel, languageCode)}
colorCoding={element.isColorCodingEnabled}
required={element.required}
+ requiredLabel={t("common.required")}
dir={dir}
imageUrl={element.imageUrl}
videoUrl={element.videoUrl}
diff --git a/packages/surveys/src/components/i18n/provider.tsx b/packages/surveys/src/components/i18n/provider.tsx
index e25199343a6e..001e1a55fc69 100644
--- a/packages/surveys/src/components/i18n/provider.tsx
+++ b/packages/surveys/src/components/i18n/provider.tsx
@@ -4,8 +4,17 @@ import { I18nextProvider } from "react-i18next";
import i18n from "../../lib/i18n.config";
export const I18nProvider = ({ language, children }: { language: string; children?: ComponentChildren }) => {
- useEffect(() => {
+ // Set language synchronously on initial render so children get the correct translations immediately.
+ // This is safe because all translations are pre-loaded (bundled) in i18n.config.ts.
+ if (i18n.language !== language) {
i18n.changeLanguage(language);
+ }
+
+ // Handle language prop changes after initial render
+ useEffect(() => {
+ if (i18n.language !== language) {
+ i18n.changeLanguage(language);
+ }
}, [language]);
// work around for react-i18next not supporting preact
diff --git a/packages/types/storage.ts b/packages/types/storage.ts
index e7d97af920a3..1ef0cc5f783f 100644
--- a/packages/types/storage.ts
+++ b/packages/types/storage.ts
@@ -22,6 +22,7 @@ export const ZAllowedFileExtension = z.enum([
"avi",
"mkv",
"webm",
+ "mp3",
"zip",
"rar",
"7z",
@@ -50,6 +51,7 @@ export const mimeTypes: Record = {
avi: "video/x-msvideo",
mkv: "video/x-matroska",
webm: "video/webm",
+ mp3: "audio/mpeg",
zip: "application/zip",
rar: "application/vnd.rar",
"7z": "application/x-7z-compressed",