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",