feat: Full RTL support for Multi-Language (#2727)

Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
Co-authored-by: Johannes <johannes@formbricks.com>
Co-authored-by: pandeymangg <anshuman.pandey9999@gmail.com>
This commit is contained in:
Piyush Gupta
2024-06-19 12:40:28 +05:30
committed by GitHub
parent d33efa0274
commit 6f2a4b2b03
34 changed files with 147 additions and 92 deletions

View File

@@ -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
<MdxImage
src={SurveyLanguageSettings}
@@ -58,7 +58,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"
/>
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.
<MdxImage
src={AddLanguages}

View File

@@ -177,7 +177,7 @@ export const HowToSendCard = ({ localSurvey, setLocalSurvey, environment }: HowT
<AlertCircleIcon className="h-5 w-5 text-amber-500" />
<div className=" text-amber-800">
<p className="text-xs font-semibold">
Your ${option.id} is not yet connected to Formbricks.
Your {option.id} is not yet connected to Formbricks.
</p>
<p className="text-xs font-normal">
<Link

View File

@@ -168,13 +168,13 @@ export const QuestionCard = ({
className="flex-1 rounded-r-lg border border-slate-200">
<Collapsible.CollapsibleTrigger
asChild
className={cn(open ? "" : " ", "flex cursor-pointer justify-between p-4 hover:bg-slate-50")}>
className={cn(open ? "" : " ", "flex cursor-pointer justify-between gap-4 p-4 hover:bg-slate-50")}>
<div>
<div className="inline-flex">
<div className="flex grow">
<div className="-ml-0.5 mr-3 h-6 min-w-[1.5rem] text-slate-400">
{QUESTIONS_ICON_MAP[question.type]}
</div>
<div>
<div className="grow" dir="auto">
<p className="text-sm font-semibold">
{recallToHeadline(
question.headline,

View File

@@ -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);

View File

@@ -70,11 +70,8 @@ export const SelectQuestionChoice = ({
return (
<div className="flex w-full gap-2" ref={setNodeRef} style={style}>
{/* drag handle */}
<div
className={cn("flex items-center", choice.id === "other" && "invisible")}
{...listeners}
{...attributes}>
<GripVerticalIcon className="mt-3 h-4 w-4 cursor-move text-slate-400" />
<div className={cn("mt-6", choice.id === "other" && "invisible")} {...listeners} {...attributes}>
<GripVerticalIcon className="h-4 w-4 cursor-move text-slate-400" />
</div>
<div className="flex w-full space-x-2">

View File

@@ -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" : ""}`}
/>

View File

@@ -243,7 +243,7 @@ export const BasicSegmentSettings = ({
handleUpdateSegment();
}}
disabled={isSaveDisabled}>
Save Changes
Save changes
</Button>
</div>

View File

@@ -82,7 +82,7 @@ export const MultipleChoiceSummary = ({
.filter((otherValue) => otherValue.value !== "")
.slice(0, visibleOtherResponses)
.map((otherValue, idx) => (
<div key={idx}>
<div key={idx} dir="auto">
{surveyType === "link" && (
<div
key={idx}

View File

@@ -69,7 +69,7 @@ export const OpenTextSummary = ({
</div>
)}
</div>
<div className="ph-no-capture col-span-2 whitespace-pre-wrap pl-6 font-semibold">
<div className="ph-no-capture col-span-2 whitespace-pre-wrap pl-6 font-semibold" dir="auto">
{response.value}
</div>
<div className="px-4 text-slate-500 md:px-6">

View File

@@ -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();

View File

@@ -231,7 +231,7 @@ export function SegmentSettings({
}}
type="submit"
variant="darkCTA">
Save Changes
Save changes
</Button>
{isDeleteSegmentModalOpen ? (

View File

@@ -144,8 +144,8 @@ export function EditLanguage({ product, environmentId }: EditLanguageProps) {
const AddLanguageButton: React.FC<{ onClick: () => void }> = ({ onClick }) =>
isEditing && languages.length === product.languages.length ? (
<Button onClick={onClick} size="sm" variant="secondary">
<PlusIcon /> Add Language
<Button onClick={onClick} size="sm" variant="secondary" StartIcon={PlusIcon}>
Add language
</Button>
) : null;
@@ -236,7 +236,7 @@ const EditSaveButtons: React.FC<{
isEditing ? (
<div className="flex gap-4">
<Button onClick={onSave} size="sm" variant="darkCTA">
Save Changes
Save changes
</Button>
<Button onClick={onCancel} size="sm" variant="minimal">
Cancel
@@ -244,6 +244,6 @@ const EditSaveButtons: React.FC<{
</div>
) : (
<Button className="w-fit" onClick={onEdit} size="sm" variant="darkCTA">
Edit Languages
Edit languages
</Button>
);

View File

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

View File

@@ -9,6 +9,7 @@ interface BackButtonProps {
export const BackButton = ({ onClick, backButtonLabel, tabIndex = 2 }: BackButtonProps) => {
return (
<button
dir="auto"
tabIndex={tabIndex}
type={"button"}
className={cn(

View File

@@ -30,6 +30,7 @@ export const SubmitButton = ({
return (
<button
dir="auto"
ref={buttonRef}
type={type}
tabIndex={tabIndex}

View File

@@ -13,7 +13,9 @@ export const Headline = ({
}: HeadlineProps) => {
return (
<label htmlFor={questionId} className="text-heading mb-1.5 block text-base font-semibold leading-6">
<div className={`flex items-center ${alignTextCenter ? "justify-center" : "justify-between"}`}>
<div
className={`flex items-center ${alignTextCenter ? "justify-center" : "justify-between"}`}
dir="auto">
{headline}
{!required && (
<span

View File

@@ -1,7 +1,8 @@
import { cn } from "@/lib/utils";
import { useEffect, useState } from "react";
interface HtmlBodyProps {
htmlString: string | undefined;
htmlString?: string;
questionId: string;
}
@@ -22,7 +23,9 @@ export const HtmlBody = ({ htmlString, questionId }: HtmlBodyProps) => {
return (
<label
htmlFor={questionId}
className="fb-htmlbody break-words" // styles are in global.css
dangerouslySetInnerHTML={{ __html: safeHtml }}></label>
className={cn("fb-htmlbody break-words")} // styles are in global.css
dangerouslySetInnerHTML={{ __html: safeHtml }}
dir="auto"
/>
);
};

View File

@@ -1,6 +1,14 @@
export const Subheader = ({ subheader, questionId }: { subheader?: string; questionId: string }) => {
interface SubheaderProps {
subheader?: string;
questionId: string;
}
export const Subheader = ({ subheader, questionId }: SubheaderProps) => {
return (
<p htmlFor={questionId} className="text-subheading block break-words text-sm font-normal leading-5">
<p
htmlFor={questionId}
className="text-subheading block break-words text-sm font-normal leading-5"
dir="auto">
{subheader}
</p>
);

View File

@@ -147,6 +147,7 @@ export const AddressQuestion = ({
{inputConfig.map(({ name, placeholder, required }, index) => (
<input
ref={index === 0 ? addressTextRef : null}
dir="auto"
key={index}
name={name}
autoComplete={name}

View File

@@ -72,6 +72,7 @@ export const CTAQuestion = ({
<div className="flex w-full justify-end">
{!question.required && (
<button
dir="auto"
tabIndex={0}
type="button"
onClick={() => {

View File

@@ -66,6 +66,7 @@ export const ConsentQuestion = ({
/>
<div className="bg-survey-bg sticky -bottom-2 z-10 w-full px-1 py-1">
<label
dir="auto"
tabIndex={1}
id={`${question.id}-label`}
onKeyDown={(e) => {
@@ -94,7 +95,7 @@ export const ConsentQuestion = ({
aria-labelledby={`${question.id}-label`}
required={question.required}
/>
<span id={`${question.id}-label`} className="ml-3 font-medium">
<span id={`${question.id}-label`} className="ml-3 mr-3 font-medium">
{getLocalizedValue(question.label, languageCode)}
</span>
</label>

View File

@@ -84,7 +84,7 @@ export const MatrixQuestion = ({
const columnsHeaders = useMemo(
() =>
question.columns.map((column, index) => (
<th key={index} className="text-heading max-w-40 break-words px-4 py-2 font-normal">
<th key={index} className="text-heading max-w-40 break-words px-4 py-2 font-normal" dir="auto">
{getLocalizedValue(column, languageCode)}
</th>
)),
@@ -117,7 +117,7 @@ export const MatrixQuestion = ({
{question.rows.map((row, rowIndex) => (
// Table rows
<tr className={`${rowIndex % 2 === 0 ? "bg-input-bg" : ""}`}>
<td className="text-heading rounded-l-custom max-w-40 break-words px-4 py-2">
<td className="text-heading rounded-l-custom max-w-40 break-words px-4 py-2" dir="auto">
{getLocalizedValue(row, languageCode)}
</td>
{question.columns.map((column, columnIndex) => (
@@ -139,10 +139,12 @@ export const MatrixQuestion = ({
getLocalizedValue(row, languageCode)
);
}
}}>
}}
dir="auto">
<div className="flex items-center justify-center p-2">
{/* radio input */}
<input
dir="auto"
type="radio"
tabIndex={-1}
id={`${row}-${column}`}

View File

@@ -181,7 +181,7 @@ export const MultipleChoiceMultiQuestion = ({
}
}}
autoFocus={idx === 0 && !isInIframe}>
<span className="flex items-center text-sm">
<span className="flex items-center text-sm" dir="auto">
<input
type="checkbox"
id={choice.id}
@@ -207,7 +207,7 @@ export const MultipleChoiceMultiQuestion = ({
: question.required
}
/>
<span id={`${choice.id}-label`} className="ml-3 font-medium">
<span id={`${choice.id}-label`} className="ml-3 mr-3 grow font-medium">
{getLocalizedValue(choice.label, languageCode)}
</span>
</span>
@@ -231,7 +231,7 @@ export const MultipleChoiceMultiQuestion = ({
document.getElementById(otherOption.id)?.focus();
}
}}>
<span className="flex items-center text-sm">
<span className="flex items-center text-sm" dir="auto">
<input
type="checkbox"
tabIndex={-1}
@@ -250,13 +250,14 @@ export const MultipleChoiceMultiQuestion = ({
}}
checked={otherSelected}
/>
<span id={`${otherOption.id}-label`} className="ml-3 font-medium">
<span id={`${otherOption.id}-label`} className="ml-3 mr-3 grow font-medium">
{getLocalizedValue(otherOption.label, languageCode)}
</span>
</span>
{otherSelected && (
<input
ref={otherSpecify}
dir="auto"
id={`${otherOption.id}-label`}
name={question.id}
tabIndex={questionChoices.length + 1}

View File

@@ -128,6 +128,7 @@ export const MultipleChoiceSingleQuestion = ({
if (!choice || choice.id === "other") return;
return (
<label
dir="auto"
tabIndex={idx + 1}
key={choice.id}
className={cn(
@@ -152,6 +153,7 @@ export const MultipleChoiceSingleQuestion = ({
id={choice.id}
name={question.id}
value={getLocalizedValue(choice.label, languageCode)}
dir="auto"
className="border-brand text-brand h-4 w-4 border focus:ring-0 focus:ring-offset-0"
aria-labelledby={`${choice.id}-label`}
onChange={() => {
@@ -161,7 +163,7 @@ export const MultipleChoiceSingleQuestion = ({
checked={value === getLocalizedValue(choice.label, languageCode)}
required={question.required && idx === 0}
/>
<span id={`${choice.id}-label`} className="ml-3 font-medium">
<span id={`${choice.id}-label`} className="ml-3 mr-3 grow font-medium">
{getLocalizedValue(choice.label, languageCode)}
</span>
</span>
@@ -170,6 +172,7 @@ export const MultipleChoiceSingleQuestion = ({
})}
{otherOption && (
<label
dir="auto"
tabIndex={questionChoices.length + 1}
className={cn(
value === getLocalizedValue(otherOption.label, languageCode)
@@ -187,6 +190,7 @@ export const MultipleChoiceSingleQuestion = ({
}}>
<span className="flex items-center text-sm">
<input
dir="auto"
type="radio"
id={otherOption.id}
tabIndex={-1}
@@ -200,7 +204,7 @@ export const MultipleChoiceSingleQuestion = ({
}}
checked={otherSelected}
/>
<span id={`${otherOption.id}-label`} className="ml-3 font-medium">
<span id={`${otherOption.id}-label`} className="ml-3 mr-3 grow font-medium" dir="auto">
{getLocalizedValue(otherOption.label, languageCode)}
</span>
</span>
@@ -209,6 +213,7 @@ export const MultipleChoiceSingleQuestion = ({
ref={otherSpecify}
tabIndex={questionChoices.length + 1}
id={`${otherOption.id}-label`}
dir="auto"
name={question.id}
value={value}
onChange={(e) => {

View File

@@ -122,8 +122,8 @@ export const NPSQuestion = ({
})}
</div>
<div className="text-subheading mt-2 flex justify-between px-1.5 text-xs leading-6">
<p>{getLocalizedValue(question.lowerLabel, languageCode)}</p>
<p>{getLocalizedValue(question.upperLabel, languageCode)}</p>
<p dir="auto">{getLocalizedValue(question.lowerLabel, languageCode)}</p>
<p dir="auto">{getLocalizedValue(question.upperLabel, languageCode)}</p>
</div>
</fieldset>
</div>

View File

@@ -99,6 +99,7 @@ export const OpenTextQuestion = ({
name={question.id}
id={question.id}
placeholder={getLocalizedValue(question.placeholder, languageCode)}
dir="auto"
step={"any"}
required={question.required}
value={value ? (value as string) : ""}
@@ -118,6 +119,7 @@ export const OpenTextQuestion = ({
aria-label="textarea"
id={question.id}
placeholder={getLocalizedValue(question.placeholder, languageCode)}
dir="auto"
required={question.required}
value={value as string}
type={question.inputType}

View File

@@ -206,8 +206,12 @@ export const RatingQuestion = ({
))}
</div>
<div className="text-subheading mt-4 flex justify-between px-1.5 text-xs leading-6">
<p className="w-1/2 text-left">{getLocalizedValue(question.lowerLabel, languageCode)}</p>
<p className="w-1/2 text-right">{getLocalizedValue(question.upperLabel, languageCode)}</p>
<p className="w-1/2 text-left" dir="auto">
{getLocalizedValue(question.lowerLabel, languageCode)}
</p>
<p className="w-1/2 text-right" dir="auto">
{getLocalizedValue(question.upperLabel, languageCode)}
</p>
</div>
</fieldset>
</div>

View File

@@ -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;
}
}
}

View File

@@ -4,7 +4,7 @@ interface AddressResponseProps {
export const AddressResponse = ({ value }: AddressResponseProps) => {
return (
<div className="my-1 font-semibold text-slate-700">
<div className="my-1 font-semibold text-slate-700" dir="auto">
{value.map(
(item, index) =>
item && (

View File

@@ -63,6 +63,7 @@ const editorConfig = {
export const Editor = (props: TextEditorProps) => {
const editable = props.editable ?? true;
return (
<div className="editor cursor-text rounded-md">
<LexicalComposer initialConfig={{ ...editorConfig, editable }}>

View File

@@ -1,4 +1,6 @@
export const exampleTheme = {
rtl: "fb-editor-rtl",
ltr: "fb-editor-ltr",
placeholder: "fb-editor-placeholder",
paragraph: "fb-editor-paragraph",
heading: {

View File

@@ -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;
}

View File

@@ -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<TSurveyRecallItem[]>(
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 = ({
<div
id="wrapper"
ref={highlightContainerRef}
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 ">
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}
</div>
{getLocalizedValue(getElementTextBasedOnType(), selectedLanguageCode).includes("recall:") && (
{getLocalizedValue(getElementTextBasedOnType(), usedLanguageCode).includes("recall:") && (
<button
className="fixed right-14 hidden items-center rounded-b-lg bg-slate-100 px-2.5 py-1 text-xs hover:bg-slate-200 group-hover:flex"
onClick={(e) => {
@@ -439,7 +457,8 @@ export const QuestionFormInput = ({
</button>
)}
<Input
key={`${questionId}-${id}-${selectedLanguageCode}`}
key={`${questionId}-${id}-${usedLanguageCode}`}
dir="auto"
className={`absolute top-0 text-black caret-black ${localSurvey.languages?.length > 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 && (
<LanguageIndicator
selectedLanguageCode={selectedLanguageCode}
selectedLanguageCode={usedLanguageCode}
surveyLanguages={enabledLanguages}
setSelectedLanguageCode={setSelectedLanguageCode}
/>
@@ -520,19 +533,19 @@ export const QuestionFormInput = ({
addRecallItem={addRecallItem}
setShowRecallItemSelect={setShowRecallItemSelect}
recallItems={recallItems}
selectedLanguageCode={selectedLanguageCode}
selectedLanguageCode={usedLanguageCode}
hiddenFields={localSurvey.hiddenFields}
attributeClasses={attributeClasses}
/>
)}
</div>
{selectedLanguageCode !== "default" && value && typeof value["default"] !== undefined && (
{usedLanguageCode !== "default" && value && typeof value["default"] !== undefined && (
<div className="mt-1 text-xs text-gray-500">
<strong>Translate:</strong>{" "}
{recallToHeadline(value, localSurvey, false, "default", attributeClasses)["default"]}
</div>
)}
{selectedLanguageCode === "default" && localSurvey.languages?.length > 1 && isTranslationIncomplete && (
{usedLanguageCode === "default" && localSurvey.languages?.length > 1 && isTranslationIncomplete && (
<div className="mt-1 text-xs text-red-400">Contains Incomplete translations</div>
)}
</div>

View File

@@ -157,7 +157,7 @@ export const SingleResponseCardBody = ({
)
)}
</p>
{renderResponse(question.type, response.data[question.id], question)}
<div dir="auto">{renderResponse(question.type, response.data[question.id], question)}</div>
</div>
) : (
<QuestionSkip