diff --git a/apps/docs/app/global/multi-language-surveys/page.mdx b/apps/docs/app/global/multi-language-surveys/page.mdx index b11383f482..c6a84e40ca 100644 --- a/apps/docs/app/global/multi-language-surveys/page.mdx +++ b/apps/docs/app/global/multi-language-surveys/page.mdx @@ -49,7 +49,7 @@ How to deliver a specific language depends on the survey type (app or link surve className="max-w-full rounded-lg sm:max-w-3xl" /> -2. Click on the **Edit Languages** button, to add a new language to your survey +2. Click on the **Edit languages** button, to add a new language to your survey -3. Select the preferred language from the dropdown and assign an identifier Alias. Click the **Add Language** button to add the language to your product. +3. Select the preferred language from the dropdown and assign an identifier Alias. Click the **Add language** button to add the language to your product.

- Your ${option.id} is not yet connected to Formbricks. + Your {option.id} is not yet connected to Formbricks.

+ className={cn(open ? "" : " ", "flex cursor-pointer justify-between gap-4 p-4 hover:bg-slate-50")}>

-
+
{QUESTIONS_ICON_MAP[question.type]}
-
+

{recallToHeadline( question.headline, diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionMenu.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionMenu.tsx index bb60634940..e22b56d065 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionMenu.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionMenu.tsx @@ -17,7 +17,7 @@ import { DropdownMenuTrigger, } from "@formbricks/ui/DropdownMenu"; -interface QuestionDropdownProps { +interface QuestionMenuProps { questionIdx: number; lastQuestion: boolean; duplicateQuestion: (questionIdx: number) => void; @@ -39,7 +39,7 @@ export const QuestionMenu = ({ question, updateQuestion, addQuestion, -}: QuestionDropdownProps) => { +}: QuestionMenuProps) => { const [logicWarningModal, setLogicWarningModal] = useState(false); const [changeToType, setChangeToType] = useState(question.type); diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SelectQuestionChoice.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SelectQuestionChoice.tsx index cfb932044d..4cc334235b 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SelectQuestionChoice.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SelectQuestionChoice.tsx @@ -70,11 +70,8 @@ export const SelectQuestionChoice = ({ return (

{/* drag handle */} -
- +
+
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/UpdateQuestionId.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/UpdateQuestionId.tsx index 5eb8a30ce8..7e65f2e356 100644 --- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/UpdateQuestionId.tsx +++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/UpdateQuestionId.tsx @@ -61,6 +61,7 @@ export const UpdateQuestionId = ({ onChange={(e) => { setCurrentValue(e.target.value); }} + dir="auto" disabled={localSurvey.status !== "draft" && !question.isDraft} className={`h-10 ${isInputInvalid ? "border-red-300 focus:border-red-300" : ""}`} /> diff --git a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/BasicSegmentSettings.tsx b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/BasicSegmentSettings.tsx index d622d03895..72433955cd 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/BasicSegmentSettings.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/components/BasicSegmentSettings.tsx @@ -243,7 +243,7 @@ export const BasicSegmentSettings = ({ handleUpdateSegment(); }} disabled={isSaveDisabled}> - Save Changes + Save changes
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MultipleChoiceSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MultipleChoiceSummary.tsx index 42bc4be3de..aa2b933459 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MultipleChoiceSummary.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/MultipleChoiceSummary.tsx @@ -82,7 +82,7 @@ export const MultipleChoiceSummary = ({ .filter((otherValue) => otherValue.value !== "") .slice(0, visibleOtherResponses) .map((otherValue, idx) => ( -
+
{surveyType === "link" && (
)}
-
+
{response.value}
diff --git a/apps/web/playwright/survey.spec.ts b/apps/web/playwright/survey.spec.ts index c522f4eda2..457b0ca747 100644 --- a/apps/web/playwright/survey.spec.ts +++ b/apps/web/playwright/survey.spec.ts @@ -191,20 +191,20 @@ test.describe("Multi Language Survey Create", async () => { //add a new language await page.getByRole("link", { name: "Configuration" }).click(); await page.getByRole("link", { name: "Survey Languages" }).click(); - await page.getByRole("button", { name: "Edit Languages" }).click(); - await page.getByRole("button", { name: "Add Language" }).click(); + await page.getByRole("button", { name: "Edit languages" }).click(); + await page.getByRole("button", { name: "Add language" }).click(); await page.getByRole("button", { name: "Select" }).click(); await page.getByPlaceholder("Search items").click(); await page.getByPlaceholder("Search items").fill("Eng"); await page.getByText("English").click(); - await page.getByRole("button", { name: "Save Changes" }).click(); - await page.getByRole("button", { name: "Edit Languages" }).click(); - await page.getByRole("button", { name: "Add Language" }).click(); + await page.getByRole("button", { name: "Save changes" }).click(); + await page.getByRole("button", { name: "Edit languages" }).click(); + await page.getByRole("button", { name: "Add language" }).click(); await page.getByRole("button", { name: "Select" }).click(); await page.getByRole("textbox", { name: "Search items" }).click(); await page.getByRole("textbox", { name: "Search items" }).fill("German"); await page.getByText("German").nth(1).click(); - await page.getByRole("button", { name: "Save Changes" }).click(); + await page.getByRole("button", { name: "Save changes" }).click(); await page.waitForTimeout(2000); await page.getByRole("link", { name: "Surveys" }).click(); await page.getByRole("button", { name: "Start from scratch Create a" }).click(); diff --git a/packages/ee/advanced-targeting/components/segment-settings.tsx b/packages/ee/advanced-targeting/components/segment-settings.tsx index be5aa9e87e..59e248a55b 100644 --- a/packages/ee/advanced-targeting/components/segment-settings.tsx +++ b/packages/ee/advanced-targeting/components/segment-settings.tsx @@ -231,7 +231,7 @@ export function SegmentSettings({ }} type="submit" variant="darkCTA"> - Save Changes + Save changes {isDeleteSegmentModalOpen ? ( diff --git a/packages/ee/multi-language/components/edit-language.tsx b/packages/ee/multi-language/components/edit-language.tsx index 4043e411f6..1bfa6a51ef 100644 --- a/packages/ee/multi-language/components/edit-language.tsx +++ b/packages/ee/multi-language/components/edit-language.tsx @@ -144,8 +144,8 @@ export function EditLanguage({ product, environmentId }: EditLanguageProps) { const AddLanguageButton: React.FC<{ onClick: () => void }> = ({ onClick }) => isEditing && languages.length === product.languages.length ? ( - ) : null; @@ -236,7 +236,7 @@ const EditSaveButtons: React.FC<{ isEditing ? (
) : ( ); diff --git a/packages/lib/response/utils.ts b/packages/lib/response/utils.ts index 380eb2e7e4..3a254c0e4b 100644 --- a/packages/lib/response/utils.ts +++ b/packages/lib/response/utils.ts @@ -667,7 +667,7 @@ const checkForI18n = (response: TResponse, id: string, survey: TSurvey, language (typeof response.data[id] === "string" ? ([response.data[id]] as string[]) : (response.data[id] as string[]) - ).forEach((data) => { + )?.forEach((data) => { choiceValues.push( getLocalizedValue( question.choices.find((choice) => choice.label[languageCode] === data)?.label, diff --git a/packages/surveys/src/components/buttons/BackButton.tsx b/packages/surveys/src/components/buttons/BackButton.tsx index 6e5136d263..90597248bf 100644 --- a/packages/surveys/src/components/buttons/BackButton.tsx +++ b/packages/surveys/src/components/buttons/BackButton.tsx @@ -9,6 +9,7 @@ interface BackButtonProps { export const BackButton = ({ onClick, backButtonLabel, tabIndex = 2 }: BackButtonProps) => { return (
-

{getLocalizedValue(question.lowerLabel, languageCode)}

-

{getLocalizedValue(question.upperLabel, languageCode)}

+

+ {getLocalizedValue(question.lowerLabel, languageCode)} +

+

+ {getLocalizedValue(question.upperLabel, languageCode)} +

diff --git a/packages/surveys/src/styles/global.css b/packages/surveys/src/styles/global.css index 7141962e9c..1a93f0a715 100644 --- a/packages/surveys/src/styles/global.css +++ b/packages/surveys/src/styles/global.css @@ -36,6 +36,7 @@ /* need to use !important because in packages/ui/components/editor/stylesEditorFrontend.css the color is defined for some classes */ color: var(--fb-subheading-color) !important; } + /* without this, it wont override the color */ p.fb-editor-paragraph { color: var(--fb-subheading-color) !important; @@ -106,8 +107,8 @@ p.fb-editor-paragraph { } .no-scrollbar { - -ms-overflow-style: none !important; /* Internet Explorer 10+ */ - scrollbar-width: thin !important; /* Firefox */ + -ms-overflow-style: none !important; /* Internet Explorer 10+ */ + scrollbar-width: thin !important; /* Firefox */ scrollbar-color: transparent transparent !important; /* Firefox */ /* Chrome, Edge, and Safari */ @@ -118,4 +119,4 @@ p.fb-editor-paragraph { &::-webkit-scrollbar-thumb { background: transparent !important; } -} \ No newline at end of file +} diff --git a/packages/ui/AddressResponse/index.tsx b/packages/ui/AddressResponse/index.tsx index d7874b8ef1..a86408ab5d 100644 --- a/packages/ui/AddressResponse/index.tsx +++ b/packages/ui/AddressResponse/index.tsx @@ -4,7 +4,7 @@ interface AddressResponseProps { export const AddressResponse = ({ value }: AddressResponseProps) => { return ( -
+
{value.map( (item, index) => item && ( diff --git a/packages/ui/Editor/components/Editor.tsx b/packages/ui/Editor/components/Editor.tsx index 5f553aac16..54236891b6 100644 --- a/packages/ui/Editor/components/Editor.tsx +++ b/packages/ui/Editor/components/Editor.tsx @@ -63,6 +63,7 @@ const editorConfig = { export const Editor = (props: TextEditorProps) => { const editable = props.editable ?? true; + return (
diff --git a/packages/ui/Editor/lib/ExampleTheme.ts b/packages/ui/Editor/lib/ExampleTheme.ts index 779316ce08..fdf24a1b77 100644 --- a/packages/ui/Editor/lib/ExampleTheme.ts +++ b/packages/ui/Editor/lib/ExampleTheme.ts @@ -1,4 +1,6 @@ export const exampleTheme = { + rtl: "fb-editor-rtl", + ltr: "fb-editor-ltr", placeholder: "fb-editor-placeholder", paragraph: "fb-editor-paragraph", heading: { diff --git a/packages/ui/Editor/stylesEditorFrontend.css b/packages/ui/Editor/stylesEditorFrontend.css index 057101556c..19a5a60813 100644 --- a/packages/ui/Editor/stylesEditorFrontend.css +++ b/packages/ui/Editor/stylesEditorFrontend.css @@ -53,3 +53,11 @@ .fb-editor-nested-listitem { list-style-type: none !important; } + +.fb-editor-rtl { + text-align: right !important; +} + +.fb-editor-ltr { + text-align: left !important; +} diff --git a/packages/ui/QuestionFormInput/index.tsx b/packages/ui/QuestionFormInput/index.tsx index 1b92db17bb..731f1a8864 100644 --- a/packages/ui/QuestionFormInput/index.tsx +++ b/packages/ui/QuestionFormInput/index.tsx @@ -82,6 +82,10 @@ export const QuestionFormInput = ({ className, attributeClasses, }: QuestionFormInputProps) => { + const defaultLanguageCode = + localSurvey.languages.filter((lang) => lang.default)[0]?.language.code ?? "default"; + const usedLanguageCode = selectedLanguageCode === defaultLanguageCode ? "default" : selectedLanguageCode; + const question: TSurveyQuestion = localSurvey.questions[questionIdx]; const isChoice = id.includes("choice"); const isMatrixLabelRow = id.includes("row"); @@ -135,18 +139,18 @@ export const QuestionFormInput = ({ const [showRecallItemSelect, setShowRecallItemSelect] = useState(false); const [showFallbackInput, setShowFallbackInput] = useState(false); const [recallItems, setRecallItems] = useState( - getLocalizedValue(text, selectedLanguageCode).includes("#recall:") + getLocalizedValue(text, usedLanguageCode).includes("#recall:") ? getRecallItems( - getLocalizedValue(text, selectedLanguageCode), + getLocalizedValue(text, usedLanguageCode), localSurvey, - selectedLanguageCode, + usedLanguageCode, attributeClasses ) : [] ); const [fallbacks, setFallbacks] = useState<{ [type: string]: string }>( - getLocalizedValue(text, selectedLanguageCode).includes("/fallback:") - ? getFallbackValues(getLocalizedValue(text, selectedLanguageCode)) + getLocalizedValue(text, usedLanguageCode).includes("/fallback:") + ? getFallbackValues(getLocalizedValue(text, usedLanguageCode)) : {} ); @@ -161,6 +165,19 @@ export const QuestionFormInput = ({ // Hook to synchronize the horizontal scroll position of highlightContainerRef and inputRef. useSyncScroll(highlightContainerRef, inputRef); + useEffect(() => { + setRecallItems( + getLocalizedValue(text, usedLanguageCode).includes("#recall:") + ? getRecallItems( + getLocalizedValue(text, usedLanguageCode), + localSurvey, + usedLanguageCode, + attributeClasses + ) + : [] + ); + }, [usedLanguageCode]); + useEffect(() => { if (id === "headline" || id === "subheader") { checkForRecallSymbol(); @@ -187,8 +204,8 @@ export const QuestionFormInput = ({ // Constructs an array of JSX elements representing segmented parts of text, interspersed with special formatted spans for recall headlines. const processInput = (): JSX.Element[] => { const parts: JSX.Element[] = []; - let remainingText = recallToHeadline(text, localSurvey, false, selectedLanguageCode, attributeClasses)[ - selectedLanguageCode + let remainingText = recallToHeadline(text, localSurvey, false, usedLanguageCode, attributeClasses)[ + usedLanguageCode ]; filterRecallItems(remainingText); recallItemLabels.forEach((label) => { @@ -221,7 +238,7 @@ export const QuestionFormInput = ({ return parts; }; setRenderedText(processInput()); - }, [text]); + }, [text, recallItems]); useEffect(() => { if (fallbackInputRef.current) { @@ -235,7 +252,7 @@ export const QuestionFormInput = ({ const checkForRecallSymbol = () => { const pattern = /(^|\s)@(\s|$)/; - if (pattern.test(getLocalizedValue(text, selectedLanguageCode))) { + if (pattern.test(getLocalizedValue(text, usedLanguageCode))) { setShowRecallItemSelect(true); } else { setShowRecallItemSelect(false); @@ -262,16 +279,16 @@ export const QuestionFormInput = ({ } setShowRecallItemSelect(false); let modifiedHeadlineWithId = { ...getElementTextBasedOnType() }; - modifiedHeadlineWithId[selectedLanguageCode] = getLocalizedValue( + modifiedHeadlineWithId[usedLanguageCode] = getLocalizedValue( modifiedHeadlineWithId, - selectedLanguageCode + usedLanguageCode ).replace(/(?<=^|\s)@(?=\s|$)/g, `#recall:${recallItem.id}/fallback:# `); - handleUpdate(getLocalizedValue(modifiedHeadlineWithId, selectedLanguageCode)); + handleUpdate(getLocalizedValue(modifiedHeadlineWithId, usedLanguageCode)); const modifiedHeadlineWithName = recallToHeadline( modifiedHeadlineWithId, localSurvey, false, - selectedLanguageCode, + usedLanguageCode, attributeClasses ); setText(modifiedHeadlineWithName); @@ -287,15 +304,15 @@ export const QuestionFormInput = ({ } else { const recallItemToRemove = recallItem.label.slice(0, -1); const newText = { ...text }; - newText[selectedLanguageCode] = text[selectedLanguageCode].replace(`@${recallItemToRemove}`, ""); + newText[usedLanguageCode] = text[usedLanguageCode].replace(`@${recallItemToRemove}`, ""); setText(newText); - handleUpdate(text[selectedLanguageCode].replace(`@${recallItemToRemove}`, "")); + handleUpdate(text[usedLanguageCode].replace(`@${recallItemToRemove}`, "")); let updatedFallback = { ...fallbacks }; delete updatedFallback[recallItem.id]; setFallbacks(updatedFallback); + setRecallItems(includedRecallItems); } }); - setRecallItems(includedRecallItems); }; const addFallback = () => { @@ -303,7 +320,7 @@ export const QuestionFormInput = ({ filteredRecallItems.forEach((recallQuestion) => { if (recallQuestion) { const recallInfo = findRecallInfoById( - getLocalizedValue(headlineWithFallback, selectedLanguageCode), + getLocalizedValue(headlineWithFallback, usedLanguageCode), recallQuestion!.id ); if (recallInfo) { @@ -312,11 +329,11 @@ export const QuestionFormInput = ({ let updatedFallback = { ...fallbacks }; updatedFallback[recallQuestion.id] = fallBackValue; setFallbacks(updatedFallback); - headlineWithFallback[selectedLanguageCode] = getLocalizedValue( + headlineWithFallback[usedLanguageCode] = getLocalizedValue( headlineWithFallback, - selectedLanguageCode + usedLanguageCode ).replace(recallInfo, `#recall:${recallQuestion?.id}/fallback:${fallBackValue}#`); - handleUpdate(getLocalizedValue(headlineWithFallback, selectedLanguageCode)); + handleUpdate(getLocalizedValue(headlineWithFallback, usedLanguageCode)); } } }); @@ -347,7 +364,7 @@ export const QuestionFormInput = ({ const createUpdatedText = (updatedText: string): TI18nString => { return { ...getElementTextBasedOnType(), - [selectedLanguageCode]: updatedText, + [usedLanguageCode]: updatedText, }; }; @@ -424,10 +441,11 @@ export const QuestionFormInput = ({
+ className={`no-scrollbar absolute top-0 z-0 mt-0.5 flex h-10 w-full overflow-scroll whitespace-nowrap px-3 py-2 text-center text-sm text-transparent ${localSurvey.languages?.length > 1 ? "pr-24" : ""}`} + dir="auto"> {renderedText}
- {getLocalizedValue(getElementTextBasedOnType(), selectedLanguageCode).includes("recall:") && ( + {getLocalizedValue(getElementTextBasedOnType(), usedLanguageCode).includes("recall:") && ( )} 1 ? "pr-24" : ""} ${className}`} placeholder={placeholder ? placeholder : getPlaceHolderById(id)} id={id} @@ -447,8 +466,8 @@ export const QuestionFormInput = ({ aria-label={label} autoComplete={showRecallItemSelect ? "off" : "on"} value={ - recallToHeadline(text, localSurvey, false, selectedLanguageCode, attributeClasses)[ - selectedLanguageCode + recallToHeadline(text, localSurvey, false, usedLanguageCode, attributeClasses)[ + usedLanguageCode ] } ref={inputRef} @@ -456,30 +475,24 @@ export const QuestionFormInput = ({ onChange={(e) => { let translatedText = { ...getElementTextBasedOnType(), - [selectedLanguageCode]: e.target.value, + [usedLanguageCode]: e.target.value, }; setText( - recallToHeadline( - translatedText, - localSurvey, - false, - selectedLanguageCode, - attributeClasses - ) + recallToHeadline(translatedText, localSurvey, false, usedLanguageCode, attributeClasses) ); handleUpdate(headlineToRecall(e.target.value, recallItems, fallbacks)); }} maxLength={maxLength ?? undefined} isInvalid={ isInvalid && - text[selectedLanguageCode]?.trim() === "" && + text[usedLanguageCode]?.trim() === "" && localSurvey.languages?.length > 1 && isTranslationIncomplete } /> {enabledLanguages.length > 1 && ( @@ -520,19 +533,19 @@ export const QuestionFormInput = ({ addRecallItem={addRecallItem} setShowRecallItemSelect={setShowRecallItemSelect} recallItems={recallItems} - selectedLanguageCode={selectedLanguageCode} + selectedLanguageCode={usedLanguageCode} hiddenFields={localSurvey.hiddenFields} attributeClasses={attributeClasses} /> )}
- {selectedLanguageCode !== "default" && value && typeof value["default"] !== undefined && ( + {usedLanguageCode !== "default" && value && typeof value["default"] !== undefined && (
Translate:{" "} {recallToHeadline(value, localSurvey, false, "default", attributeClasses)["default"]}
)} - {selectedLanguageCode === "default" && localSurvey.languages?.length > 1 && isTranslationIncomplete && ( + {usedLanguageCode === "default" && localSurvey.languages?.length > 1 && isTranslationIncomplete && (
Contains Incomplete translations
)}
diff --git a/packages/ui/SingleResponseCard/components/SingleResponseCardBody.tsx b/packages/ui/SingleResponseCard/components/SingleResponseCardBody.tsx index e845e76a99..3b6c74a40b 100644 --- a/packages/ui/SingleResponseCard/components/SingleResponseCardBody.tsx +++ b/packages/ui/SingleResponseCard/components/SingleResponseCardBody.tsx @@ -157,7 +157,7 @@ export const SingleResponseCardBody = ({ ) )}

- {renderResponse(question.type, response.data[question.id], question)} +
{renderResponse(question.type, response.data[question.id], question)}
) : (