diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswersContainer.test.tsx b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswersContainer.test.tsx
index e3844afba..5d03e976e 100644
--- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswersContainer.test.tsx
+++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswersContainer.test.tsx
@@ -1,10 +1,10 @@
import React from 'react';
+
+import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
import {
render, screen, fireEvent, initializeMocks,
} from '../../../../../../testUtils';
import AnswersContainer from './AnswersContainer';
-import { ProblemTypeKeys } from '../../../../../data/constants/problem';
-// Import actions after mocking to access mocked functions
import { actions } from '../../../../../data/redux';
const { useAnswerContainer } = require('./hooks');
diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/index.jsx
index 9094cb52d..9f3070449 100644
--- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/index.jsx
+++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/index.jsx
@@ -2,8 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
+import { getProblemTypes } from '@src/editors/data/constants/problem';
import messages from './messages';
-import { ProblemTypes } from '../../../../../data/constants/problem';
import AnswersContainer from './AnswersContainer';
// This widget should be connected, grab all answers from store, update them as needed.
@@ -12,7 +12,10 @@ const AnswerWidget = ({
problemType,
}) => {
const intl = useIntl();
- const problemStaticData = ProblemTypes[problemType];
+
+ const localizedProblemTypes = getProblemTypes(intl.formatMessage);
+ const localizedProblemStaticData = localizedProblemTypes[problemType];
+
return (
@@ -20,7 +23,10 @@ const AnswerWidget = ({
- {intl.formatMessage(messages.answerHelperText, { helperText: problemStaticData.description })}
+
diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js
index 81db5e4b4..eb0ed887b 100644
--- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js
+++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js
@@ -3,18 +3,19 @@ import { useState, useEffect } from 'react';
import {
includes, isEmpty, isFinite, isNaN, isNil,
} from 'lodash';
+import {
+ ProblemTypeKeys,
+ ProblemTypes,
+ RichTextProblems,
+ ShowAnswerTypesKeys,
+ getProblemTypes,
+} from '@src/editors/data/constants/problem';
// This 'module' self-import hack enables mocking during tests.
// See src/editors/decisions/0005-internal-editor-testability-decisions.md. The whole approach to how hooks are tested
// should be re-thought and cleaned up to avoid this pattern.
// eslint-disable-next-line import/no-self-import
import * as module from './hooks';
import messages from './messages';
-import {
- ProblemTypeKeys,
- ProblemTypes,
- RichTextProblems,
- ShowAnswerTypesKeys,
-} from '../../../../../data/constants/problem';
import { fetchEditorContent } from '../hooks';
export const state = {
@@ -232,6 +233,7 @@ export const typeRowHooks = ({
typeKey,
updateField,
updateAnswer,
+ formatMessage,
}) => {
const clearPreviouslySelectedAnswers = () => {
let currentAnswerTitles;
@@ -312,8 +314,17 @@ export const typeRowHooks = ({
updateAnswersToCorrect();
}
- if (blockTitle === ProblemTypes[problemType].title) {
- setBlockTitle(ProblemTypes[typeKey].title);
+ // Check if blockTitle matches either the localized or non-localized problem type title
+ const localizedProblemTypes = formatMessage ? getProblemTypes(formatMessage) : null;
+ const currentTitle = localizedProblemTypes
+ ? localizedProblemTypes[problemType].title
+ : ProblemTypes[problemType].title;
+
+ if (blockTitle === currentTitle) {
+ const newTitle = localizedProblemTypes
+ ? localizedProblemTypes[typeKey].title
+ : ProblemTypes[typeKey].title;
+ setBlockTitle(newTitle);
}
updateField({ problemType: typeKey });
};
diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js
index 37e876a28..079506716 100644
--- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js
+++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js
@@ -1,9 +1,10 @@
import { useEffect } from 'react';
+import * as problemConstants from '@src/editors/data/constants/problem';
+import { ProblemTypeKeys, ProblemTypes } from '@src/editors/data/constants/problem';
import { MockUseState } from '../../../../../testUtils';
import messages from './messages';
import { keyStore } from '../../../../../utils';
import * as hooks from './hooks';
-import { ProblemTypeKeys, ProblemTypes } from '../../../../../data/constants/problem';
import * as editHooks from '../hooks';
jest.mock('react', () => {
@@ -381,6 +382,32 @@ describe('Problem settings hooks', () => {
expect(typeRowProps.updateAnswer).toHaveBeenNthCalledWith(3, { ...typeRowProps.answers[2], title: 'testC' });
expect(typeRowProps.updateField).toHaveBeenCalledWith({ problemType: ProblemTypeKeys.TEXTINPUT });
});
+
+ test('test typeRowHooks sets localized block title when formatMessage is provided', () => {
+ const mockSetBlockTitle = jest.fn();
+ const mockUpdateField = jest.fn();
+ const mockUpdateAnswer = jest.fn();
+ const mockFormatMessage = (msg) => `localized-${msg.id || msg.defaultMessage || msg}`;
+ const props = {
+ answers: [],
+ blockTitle: 'localized-problem.multiplechoiceresponse.title', // Simulate a localized title
+ correctAnswerCount: 1,
+ problemType: ProblemTypeKeys.SINGLESELECT,
+ setBlockTitle: mockSetBlockTitle,
+ typeKey: ProblemTypeKeys.MULTISELECT,
+ updateField: mockUpdateField,
+ updateAnswer: mockUpdateAnswer,
+ formatMessage: mockFormatMessage,
+ };
+ jest.spyOn(problemConstants, 'getProblemTypes').mockImplementation((fmt) => ({
+ [ProblemTypeKeys.SINGLESELECT]: { title: fmt({ id: 'problem.multiplechoiceresponse.title' }) },
+ [ProblemTypeKeys.MULTISELECT]: { title: fmt({ id: 'problem.choiceresponse.title' }) },
+ }));
+ const hook = hooks.typeRowHooks(props);
+ hook.onClick();
+ expect(mockSetBlockTitle).toHaveBeenCalledWith('localized-problem.choiceresponse.title');
+ expect(mockUpdateField).toHaveBeenCalledWith({ problemType: ProblemTypeKeys.MULTISELECT });
+ });
});
test('test handleConfirmEditorSwitch hook', () => {
const switchEditor = jest.fn();
diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.tsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.tsx
index f74b710a3..33f728d2e 100644
--- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.tsx
+++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.tsx
@@ -1,10 +1,11 @@
import React from 'react';
+
+import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
import {
render, screen, initializeMocks,
} from '@src/testUtils';
import * as hooks from './hooks';
import { SettingsWidgetInternal as SettingsWidget } from '.';
-import { ProblemTypeKeys } from '../../../../../data/constants/problem';
jest.mock('./settingsComponents/GeneralFeedback', () => 'GeneralFeedback');
jest.mock('./settingsComponents/GroupFeedback', () => 'GroupFeedback');
diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/TypeCard.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/TypeCard.jsx
index 2d51ae00c..4b3238d0e 100644
--- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/TypeCard.jsx
+++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/TypeCard.jsx
@@ -1,8 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
+
+import { ProblemTypeKeys, getProblemTypes } from '@src/editors/data/constants/problem';
import SettingsOption from '../SettingsOption';
-import { ProblemTypeKeys, ProblemTypes } from '../../../../../../data/constants/problem';
import messages from '../messages';
import TypeRow from './TypeRow';
@@ -16,6 +17,7 @@ const TypeCard = ({
updateAnswer,
}) => {
const intl = useIntl();
+ const localizedProblemTypes = getProblemTypes(intl.formatMessage);
const problemTypeKeysArray = Object.values(ProblemTypeKeys).filter(key => key !== ProblemTypeKeys.ADVANCED);
if (problemType === ProblemTypeKeys.ADVANCED) { return null; }
@@ -23,7 +25,7 @@ const TypeCard = ({
return (
{problemTypeKeysArray.map((typeKey, i) => (
{
const props = {
diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/TypeRow.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/TypeRow.jsx
index ec8bc78ec..f3b29f6af 100644
--- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/TypeRow.jsx
+++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/TypeRow.jsx
@@ -1,6 +1,7 @@
import React from 'react';
import { Icon } from '@openedx/paragon';
import PropTypes from 'prop-types';
+import { useIntl } from '@edx/frontend-platform/i18n';
import { Check } from '@openedx/paragon/icons';
import { typeRowHooks } from '../hooks';
@@ -19,6 +20,8 @@ const TypeRow = ({
updateField,
updateAnswer,
}) => {
+ const intl = useIntl();
+
const { onClick } = typeRowHooks({
answers,
blockTitle,
@@ -28,6 +31,7 @@ const TypeRow = ({
typeKey,
updateField,
updateAnswer,
+ formatMessage: intl.formatMessage,
});
return (
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.tsx b/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.tsx
index 030366546..b707b8276 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.tsx
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.tsx
@@ -71,6 +71,7 @@ const SelectTypeWrapper: React.FC = ({
updateField,
setBlockTitle,
defaultSettings,
+ formatMessage: intl.formatMessage,
})}
disabled={!selected}
>
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/messages.ts b/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/messages.ts
index 120968e30..ba83fa78f 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/messages.ts
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/messages.ts
@@ -27,6 +27,134 @@ const messages = defineMessages({
defaultMessage: 'Select',
description: 'Screen reader label for select button.',
},
+
+ // Problem Type Titles
+ singleSelectTitle: {
+ id: 'authoring.problemeditor.problemtype.singleselect.title',
+ defaultMessage: 'Single select',
+ description: 'Title for single select problem type',
+ },
+ multiSelectTitle: {
+ id: 'authoring.problemeditor.problemtype.multiselect.title',
+ defaultMessage: 'Multi-select',
+ description: 'Title for multi-select problem type',
+ },
+ dropdownTitle: {
+ id: 'authoring.problemeditor.problemtype.dropdown.title',
+ defaultMessage: 'Dropdown',
+ description: 'Title for dropdown problem type',
+ },
+ numericalInputTitle: {
+ id: 'authoring.problemeditor.problemtype.numeric.title',
+ defaultMessage: 'Numerical input',
+ description: 'Title for numerical input problem type',
+ },
+ textInputTitle: {
+ id: 'authoring.problemeditor.problemtype.textinput.title',
+ defaultMessage: 'Text input',
+ description: 'Title for text input problem type',
+ },
+ advancedProblemTitle: {
+ id: 'authoring.problemeditor.problemtype.advanced.title',
+ defaultMessage: 'Advanced Problem',
+ description: 'Title for advanced problem type',
+ },
+
+ // Advanced Problem Type Titles
+ blankProblemTitle: {
+ id: 'authoring.problemeditor.advancedproblemtype.blank.title',
+ defaultMessage: 'Blank problem',
+ description: 'Title for blank advanced problem type',
+ },
+ circuitSchematicTitle: {
+ id: 'authoring.problemeditor.advancedproblemtype.circuitschematic.title',
+ defaultMessage: 'Circuit schematic builder',
+ description: 'Title for circuit schematic builder advanced problem type',
+ },
+ customJavaScriptTitle: {
+ id: 'authoring.problemeditor.advancedproblemtype.jsinput.title',
+ defaultMessage: 'Custom JavaScript display and grading',
+ description: 'Title for custom JavaScript display and grading advanced problem type',
+ },
+ customPythonTitle: {
+ id: 'authoring.problemeditor.advancedproblemtype.customgrader.title',
+ defaultMessage: 'Custom Python-evaluated input',
+ description: 'Title for custom Python-evaluated input advanced problem type',
+ },
+ imageMappedTitle: {
+ id: 'authoring.problemeditor.advancedproblemtype.image.title',
+ defaultMessage: 'Image mapped input',
+ description: 'Title for image mapped input advanced problem type',
+ },
+ mathExpressionTitle: {
+ id: 'authoring.problemeditor.advancedproblemtype.formula.title',
+ defaultMessage: 'Math expression input',
+ description: 'Title for math expression input advanced problem type',
+ },
+ problemWithHintTitle: {
+ id: 'authoring.problemeditor.advancedproblemtype.problemwithhint.title',
+ defaultMessage: 'Problem with adaptive hint',
+ description: 'Title for problem with adaptive hint advanced problem type',
+ },
+
+ // Problem Type Descriptions
+ singleSelectDescription: {
+ id: 'authoring.problemeditor.problemtype.singleselect.description',
+ defaultMessage: 'Learners must select the correct answer from a list of possible options.',
+ description: 'Preview description for single select problem type',
+ },
+ multiSelectDescription: {
+ id: 'authoring.problemeditor.problemtype.multiselect.description',
+ defaultMessage: 'Learners must select all correct answers from a list of possible options.',
+ description: 'Preview description for multi-select problem type',
+ },
+ dropdownDescription: {
+ id: 'authoring.problemeditor.problemtype.dropdown.description',
+ defaultMessage: 'Learners must select the correct answer from a list of possible options',
+ description: 'Preview description for dropdown problem type',
+ },
+ numericalInputDescription: {
+ id: 'authoring.problemeditor.problemtype.numeric.description',
+ defaultMessage: 'Specify one or more correct numeric answers, submitted in a response field.',
+ description: 'Preview description for numerical input problem type',
+ },
+ textInputDescription: {
+ id: 'authoring.problemeditor.problemtype.textinput.description',
+ defaultMessage: 'Specify one or more correct text answers, including numbers and special characters, submitted in a response field.',
+ description: 'Preview description for text input problem type',
+ },
+ advancedProblemDescription: {
+ id: 'authoring.problemeditor.problemtype.advanced.description',
+ defaultMessage: 'An Advanced Problem Type',
+ description: 'Description for advanced problem type',
+ },
+
+ // Problem Type Instructions
+ singleSelectInstruction: {
+ id: 'authoring.problemeditor.problemtype.singleselect.instruction',
+ defaultMessage: 'Enter your single select answers below and select which choices are correct. Learners must choose one correct answer.',
+ description: 'Instruction for single select problem type',
+ },
+ multiSelectInstruction: {
+ id: 'authoring.problemeditor.problemtype.multiselect.instruction',
+ defaultMessage: 'Enter your multi select answers below and select which choices are correct. Learners must choose all correct answers.',
+ description: 'Instruction for multi-select problem type',
+ },
+ dropdownInstruction: {
+ id: 'authoring.problemeditor.problemtype.dropdown.instruction',
+ defaultMessage: 'Enter your dropdown answers below and select which choice is correct. Learners must select one correct answer.',
+ description: 'Instruction for dropdown problem type',
+ },
+ numericalInputInstruction: {
+ id: 'authoring.problemeditor.problemtype.numeric.instruction',
+ defaultMessage: 'Enter correct numerical input answers below. Learners must enter one correct answer.',
+ description: 'Instruction for numerical input problem type',
+ },
+ textInputInstruction: {
+ id: 'authoring.problemeditor.problemtype.textinput.instruction',
+ defaultMessage: 'Enter your text input answers below and select which choices are correct. Learners must enter one correct answer.',
+ description: 'Instruction for text input problem type',
+ },
});
export default messages;
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/AdvanceTypeSelect.test.tsx b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/AdvanceTypeSelect.test.tsx
index c6d5cb868..436968e3f 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/AdvanceTypeSelect.test.tsx
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/AdvanceTypeSelect.test.tsx
@@ -1,9 +1,10 @@
import React from 'react';
+
+import { ProblemTypeKeys, AdvanceProblems } from '@src/editors/data/constants/problem';
import {
render, screen, fireEvent, initializeMocks,
} from '../../../../../../testUtils';
import AdvanceTypeSelect from './AdvanceTypeSelect';
-import { ProblemTypeKeys, AdvanceProblems } from '../../../../../data/constants/problem';
describe('AdvanceTypeSelect', () => {
const setSelected = jest.fn();
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/AdvanceTypeSelect.tsx b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/AdvanceTypeSelect.tsx
index 5a53fd258..524bbd698 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/AdvanceTypeSelect.tsx
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/AdvanceTypeSelect.tsx
@@ -17,7 +17,7 @@ import {
AdvanceProblems,
ProblemType,
ProblemTypeKeys,
-} from '../../../../../data/constants/problem';
+} from '@src/editors/data/constants/problem';
import messages from './messages';
interface Props {
@@ -30,6 +30,7 @@ const AdvanceTypeSelect: React.FC = ({
setSelected,
}) => {
const intl = useIntl();
+
const handleChange = e => { setSelected(e.target.value); };
return (
@@ -53,12 +54,12 @@ const AdvanceTypeSelect: React.FC = ({
value={selected}
className="px-4"
>
- {Object.entries(AdvanceProblems).map(([type, data]) => {
- if (data.status !== '') {
+ {Object.entries(AdvanceProblems).map(([type, problemData]) => {
+ if (problemData.status !== '') {
return (
- {intl.formatMessage(messages.advanceProblemTypeLabel, { problemType: data.title })}
+
= ({
overlay={(
- {intl.formatMessage(messages.supportStatusTooltipMessage, { supportStatus: data.status.replace(' ', '_') })}
+ {intl.formatMessage(messages.supportStatusTooltipMessage, { supportStatus: problemData.status.replace(' ', '_') })}
)}
>
- {intl.formatMessage(messages.problemSupportStatus, { supportStatus: data.status })}
+ {intl.formatMessage(messages.problemSupportStatus, { supportStatus: problemData.status })}
@@ -81,7 +82,7 @@ const AdvanceTypeSelect: React.FC = ({
return (
- {intl.formatMessage(messages.advanceProblemTypeLabel, { problemType: data.title })}
+
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/Preview.jsx b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/Preview.jsx
index 71698b941..8db20435c 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/Preview.jsx
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/Preview.jsx
@@ -15,23 +15,25 @@ const Preview = ({
if (problemType === null) {
return null;
}
- const data = ProblemTypes[problemType];
+
+ const staticData = ProblemTypes[problemType];
+
return (
- {intl.formatMessage(messages.previewTitle, { previewTitle: data.title })}
+ problem
- {intl.formatMessage(messages.previewDescription, { previewDescription: data.previewDescription })}
+
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/Preview.test.tsx b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/Preview.test.tsx
index acb338435..4a4af6c33 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/Preview.test.tsx
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/Preview.test.tsx
@@ -1,48 +1,77 @@
import React from 'react';
-import { render, screen, initializeMocks } from '../../../../../../testUtils';
+import { IntlProvider } from '@edx/frontend-platform/i18n';
+import { render, screen } from '@testing-library/react';
import Preview from './Preview';
+import messages from './messages';
-// Mock ProblemTypes and messages
-jest.mock('../../../../../data/constants/problem', () => ({
- ProblemTypes: {
- example: {
- title: 'Example Title',
- preview: 'example.png',
- previewDescription: 'Example description',
- helpLink: 'https://help.example.com',
+// Mock ProblemTypes to provide test data
+jest.mock('@src/editors/data/constants/problem', () => {
+ const actualModule = jest.requireActual('@src/editors/data/constants/problem');
+
+ return {
+ ...actualModule,
+ ProblemTypes: {
+ multiplechoiceresponse: {
+ title: 'Single select',
+ preview: 'singleselect.png',
+ previewDescription: 'Learners must select the correct answer from a list of possible options.',
+ helpLink: 'https://help.example.com/singleselect',
+ },
},
- },
-}));
+ };
+});
+
+// Helper to render component with proper IntlProvider
+const renderWithIntl = (component: React.ReactElement) => {
+ // Convert message objects to message strings for IntlProvider
+ const messageStrings = Object.fromEntries(
+ Object.entries(messages).map(([key, value]) => [key, (value as any).defaultMessage]),
+ );
+
+ return render(
+
+ {component}
+ ,
+ );
+};
describe('Preview', () => {
- beforeEach(() => {
- initializeMocks();
- });
-
it('renders nothing if problemType is null', () => {
- const { container } = render();
- const reduxProviderDiv = container.querySelector('div[data-testid="redux-provider"]');
- expect(reduxProviderDiv?.innerHTML).toBe('');
+ const { container } = renderWithIntl();
+ expect(container.firstChild).toBeNull();
});
it('renders preview with correct data for a valid problemType', () => {
- render();
- expect(screen.getByText('Example Title problem')).toBeInTheDocument();
- expect(screen.getByText('Example description')).toBeInTheDocument();
- expect(screen.getByRole('img')).toHaveAttribute('src', 'example.png');
- expect(screen.getByRole('img')).toHaveAttribute('alt', 'A preview illustration of a null problem');
- expect(screen.getByRole('link', { name: 'Learn more in a new tab' })).toHaveAttribute('href', 'https://help.example.com');
+ renderWithIntl();
+
+ // Check that the title is rendered correctly
+ expect(screen.getByText('Single select problem')).toBeInTheDocument();
+
+ // Check that the description is rendered correctly
+ expect(screen.getByText('Learners must select the correct answer from a list of possible options.')).toBeInTheDocument();
+
+ // Check that the image has correct src attribute
+ const image = screen.getByRole('img');
+ expect(image).toHaveAttribute('src', 'singleselect.png');
+
+ // Check that the learn more link is rendered correctly
+ const link = screen.getByRole('link');
+ expect(link).toHaveAttribute('href', 'https://help.example.com/singleselect');
+ expect(link).toHaveAttribute('target', '_blank');
+ expect(screen.getByText('Learn more')).toBeInTheDocument();
});
it('renders the help link with target="_blank"', () => {
- render();
- const link = screen.getByRole('link', { name: 'Learn more in a new tab' });
+ renderWithIntl();
+ const link = screen.getByRole('link');
expect(link).toHaveAttribute('target', '_blank');
+ expect(link).toHaveAttribute('href', 'https://help.example.com/singleselect');
});
- it('renders the correct title and description', () => {
- render();
- expect(screen.getByText('Example Title problem')).toBeInTheDocument();
- expect(screen.getByText('Example description')).toBeInTheDocument();
+ it('displays the correct image source and alt text', () => {
+ renderWithIntl();
+ const image = screen.getByRole('img');
+ expect(image).toHaveAttribute('src', 'singleselect.png');
+ expect(image).toHaveAttribute('alt', 'A preview illustration of a single select problem');
});
});
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/ProblemTypeSelect.test.tsx b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/ProblemTypeSelect.test.tsx
index 742c80baf..76a94bbe0 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/ProblemTypeSelect.test.tsx
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/ProblemTypeSelect.test.tsx
@@ -1,8 +1,9 @@
import React from 'react';
+
+import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
import {
render, screen, fireEvent, initializeMocks,
} from '../../../../../../testUtils';
-import { ProblemTypeKeys } from '../../../../../data/constants/problem';
import ProblemTypeSelect from './ProblemTypeSelect';
describe('ProblemTypeSelect', () => {
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/ProblemTypeSelect.tsx b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/ProblemTypeSelect.tsx
index a87f32904..d3c323ee6 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/ProblemTypeSelect.tsx
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/ProblemTypeSelect.tsx
@@ -2,15 +2,14 @@ import React from 'react';
import { Button, Container } from '@openedx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
-// SelectableBox in paragon has a bug where you can't change selection. So we override it
-import SelectableBox from '../../../../../sharedComponents/SelectableBox';
import {
- ProblemTypes,
ProblemTypeKeys,
AdvanceProblemKeys,
AdvancedProblemType,
ProblemType,
-} from '../../../../../data/constants/problem';
+} from '@src/editors/data/constants/problem';
+// SelectableBox in paragon has a bug where you can't change selection. So we override it
+import SelectableBox from '../../../../../sharedComponents/SelectableBox';
import messages from './messages';
interface Props {
@@ -45,7 +44,7 @@ const ProblemTypeSelect: React.FC = ({
value={key}
{...settings}
>
- {ProblemTypes[key].title}
+
)
: null
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/messages.ts b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/messages.ts
index 66873921d..7c8ade410 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/messages.ts
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/content/messages.ts
@@ -17,10 +17,104 @@ const messages = defineMessages({
defaultMessage: 'Go back',
description: 'Return to the previous menu that shows basic problem types',
},
- advanceProblemTypeLabel: {
- id: 'authoring.problemEditor.advanceProblem.problemType.label',
- defaultMessage: '{problemType}',
- description: 'Label for advance problem type radio select',
+
+ // Direct problem type message pattern - replacing redundant advanceProblemTypeLabel
+ 'problemType.blankadvanced.title': {
+ id: 'authoring.problemeditor.advancedproblemtype.blank.title',
+ defaultMessage: 'Blank problem',
+ description: 'Title for blank advanced problem type',
+ },
+ 'problemType.circuitschematic.title': {
+ id: 'authoring.problemeditor.advancedproblemtype.circuitschematic.title',
+ defaultMessage: 'Circuit schematic builder',
+ description: 'Title for circuit schematic builder advanced problem type',
+ },
+ 'problemType.jsinputresponse.title': {
+ id: 'authoring.problemeditor.advancedproblemtype.jsinput.title',
+ defaultMessage: 'Custom JavaScript display and grading',
+ description: 'Title for custom JavaScript display and grading advanced problem type',
+ },
+ 'problemType.customgrader.title': {
+ id: 'authoring.problemeditor.advancedproblemtype.customgrader.title',
+ defaultMessage: 'Custom Python-evaluated input',
+ description: 'Title for custom Python-evaluated input advanced problem type',
+ },
+ 'problemType.imageresponse.title': {
+ id: 'authoring.problemeditor.advancedproblemtype.image.title',
+ defaultMessage: 'Image mapped input',
+ description: 'Title for image mapped input advanced problem type',
+ },
+ 'problemType.formularesponse.title': {
+ id: 'authoring.problemeditor.advancedproblemtype.formula.title',
+ defaultMessage: 'Math expression input',
+ description: 'Title for math expression input advanced problem type',
+ },
+ 'problemType.problemwithhint.title': {
+ id: 'authoring.problemeditor.advancedproblemtype.problemwithhint.title',
+ defaultMessage: 'Problem with adaptive hint',
+ description: 'Title for problem with adaptive hint advanced problem type',
+ },
+
+ // Basic Problem Type Messages by Key
+ 'problemType.multiplechoiceresponse.title': {
+ id: 'authoring.problemeditor.problemtype.singleselect.title',
+ defaultMessage: 'Single select',
+ description: 'Title for single select problem type',
+ },
+ 'problemType.multiplechoiceresponse.description': {
+ id: 'authoring.problemeditor.problemtype.singleselect.description',
+ defaultMessage: 'Learners must select the correct answer from a list of possible options.',
+ description: 'Preview description for single select problem type',
+ },
+ 'problemType.choiceresponse.title': {
+ id: 'authoring.problemeditor.problemtype.multiselect.title',
+ defaultMessage: 'Multi-select',
+ description: 'Title for multi-select problem type',
+ },
+ 'problemType.choiceresponse.description': {
+ id: 'authoring.problemeditor.problemtype.multiselect.description',
+ defaultMessage: 'Learners must select all correct answers from a list of possible options.',
+ description: 'Preview description for multi-select problem type',
+ },
+ 'problemType.optionresponse.title': {
+ id: 'authoring.problemeditor.problemtype.dropdown.title',
+ defaultMessage: 'Dropdown',
+ description: 'Title for dropdown problem type',
+ },
+ 'problemType.optionresponse.description': {
+ id: 'authoring.problemeditor.problemtype.dropdown.description',
+ defaultMessage: 'Learners must select the correct answer from a list of possible options',
+ description: 'Preview description for dropdown problem type',
+ },
+ 'problemType.numericalresponse.title': {
+ id: 'authoring.problemeditor.problemtype.numeric.title',
+ defaultMessage: 'Numerical input',
+ description: 'Title for numerical input problem type',
+ },
+ 'problemType.numericalresponse.description': {
+ id: 'authoring.problemeditor.problemtype.numeric.description',
+ defaultMessage: 'Specify one or more correct numeric answers, submitted in a response field.',
+ description: 'Preview description for numerical input problem type',
+ },
+ 'problemType.stringresponse.title': {
+ id: 'authoring.problemeditor.problemtype.textinput.title',
+ defaultMessage: 'Text input',
+ description: 'Title for text input problem type',
+ },
+ 'problemType.stringresponse.description': {
+ id: 'authoring.problemeditor.problemtype.textinput.description',
+ defaultMessage: 'Specify one or more correct text answers, including numbers and special characters, submitted in a response field.',
+ description: 'Preview description for text input problem type',
+ },
+ 'problemType.advanced.title': {
+ id: 'authoring.problemeditor.problemtype.advanced.title',
+ defaultMessage: 'Advanced Problem',
+ description: 'Title for advanced problem type',
+ },
+ 'problemType.advanced.description': {
+ id: 'authoring.problemeditor.problemtype.advanced.description',
+ defaultMessage: 'An Advanced Problem Type',
+ description: 'Description for advanced problem type',
},
problemSupportStatus: {
id: 'authoring.problemEditor.advanceProblem.supportStatus',
@@ -41,7 +135,7 @@ const messages = defineMessages({
in the future. They are not recommened for use in courses due to non-compliance with one
or more of the base requirements, such as testing, accessibility, internationalization,
and documentation.}
- other { }
+ other { }
}`,
description: 'Message for support status tooltip',
},
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/hooks.js b/src/editors/containers/ProblemEditor/components/SelectTypeModal/hooks.js
index 10aa2edc5..b239baa68 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/hooks.js
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/hooks.js
@@ -1,7 +1,7 @@
import { useEffect } from 'react';
import {
- AdvanceProblemKeys, AdvanceProblems, ProblemTypeKeys, ProblemTypes,
-} from '../../../../data/constants/problem';
+ AdvanceProblemKeys, AdvanceProblems, ProblemTypeKeys, ProblemTypes, getProblemTypes, getAdvanceProblems,
+} from '@src/editors/data/constants/problem';
import { snakeCaseKeys } from '../../../../utils';
import { getDataFromOlx } from '../../../../data/redux/thunkActions/problem';
@@ -10,10 +10,16 @@ export const onSelect = ({
updateField,
setBlockTitle,
defaultSettings,
+ formatMessage,
}) => () => {
if (Object.values(AdvanceProblemKeys).includes(selected)) {
updateField({ problemType: ProblemTypeKeys.ADVANCED, rawOLX: AdvanceProblems[selected].template });
- setBlockTitle(AdvanceProblems[selected].title);
+ if (formatMessage) {
+ const localizedAdvanceProblems = getAdvanceProblems(formatMessage);
+ setBlockTitle(localizedAdvanceProblems[selected].title);
+ } else {
+ setBlockTitle(AdvanceProblems[selected].title);
+ }
} else {
const newOLX = ProblemTypes[selected].template;
const newMarkdown = ProblemTypes[selected].markdownTemplate;
@@ -28,7 +34,12 @@ export const onSelect = ({
defaultSettings: snakeCaseKeys(defaultSettings),
});
updateField({ ...newState, rawMarkdown: newMarkdown });
- setBlockTitle(ProblemTypes[selected].title);
+ if (formatMessage) {
+ const localizedProblemTypes = getProblemTypes(formatMessage);
+ setBlockTitle(localizedProblemTypes[selected].title);
+ } else {
+ setBlockTitle(ProblemTypes[selected].title);
+ }
}
};
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/hooks.test.js b/src/editors/containers/ProblemEditor/components/SelectTypeModal/hooks.test.js
index 4e246f049..72a963e77 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/hooks.test.js
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/hooks.test.js
@@ -1,7 +1,9 @@
/* eslint-disable prefer-destructuring */
import React from 'react';
+
+import * as problemConstants from '@src/editors/data/constants/problem';
+import { AdvanceProblems, ProblemTypeKeys, ProblemTypes } from '@src/editors/data/constants/problem';
import * as hooks from './hooks';
-import { AdvanceProblems, ProblemTypeKeys, ProblemTypes } from '../../../../data/constants/problem';
import { getDataFromOlx } from '../../../../data/redux/thunkActions/problem';
jest.mock('react', () => ({
@@ -65,6 +67,26 @@ describe('SelectTypeModal hooks', () => {
});
expect(mocksetBlockTitle).toHaveBeenCalledWith(ProblemTypes[mockSelected].title);
});
+ test('onSelect sets localized advanced problem title when formatMessage is provided', () => {
+ const mockSetBlockTitle2 = jest.fn();
+ const mockUpdateField2 = jest.fn();
+ const mockFormatMessage2 = (msg) => `localized-${msg.id || msg.defaultMessage || msg}`;
+ const mockSelected2 = 'circuitschematic';
+ jest.spyOn(problemConstants, 'getAdvanceProblems').mockImplementation((fmt) => ({
+ circuitschematic: { title: fmt({ id: 'problem.circuitschematic.title' }) },
+ }));
+ hooks.onSelect({
+ selected: mockSelected2,
+ updateField: mockUpdateField2,
+ setBlockTitle: mockSetBlockTitle2,
+ formatMessage: mockFormatMessage2,
+ })();
+ expect(mockSetBlockTitle2).toHaveBeenCalledWith('localized-problem.circuitschematic.title');
+ expect(mockUpdateField2).toHaveBeenCalledWith({
+ problemType: ProblemTypeKeys.ADVANCED,
+ rawOLX: AdvanceProblems[mockSelected2].template,
+ });
+ });
});
describe('useArrowNav', () => {
diff --git a/src/editors/containers/ProblemEditor/components/SelectTypeModal/index.tsx b/src/editors/containers/ProblemEditor/components/SelectTypeModal/index.tsx
index fbb17fad4..1881035f9 100644
--- a/src/editors/containers/ProblemEditor/components/SelectTypeModal/index.tsx
+++ b/src/editors/containers/ProblemEditor/components/SelectTypeModal/index.tsx
@@ -1,17 +1,18 @@
import React from 'react';
import { Row, Stack } from '@openedx/paragon';
-import ProblemTypeSelect from './content/ProblemTypeSelect';
-import Preview from './content/Preview';
-import AdvanceTypeSelect from './content/AdvanceTypeSelect';
-import SelectTypeWrapper from './SelectTypeWrapper';
-import * as hooks from './hooks';
+
import {
AdvancedProblemType,
isAdvancedProblemType,
ProblemType,
ProblemTypeKeys,
-} from '../../../../data/constants/problem';
+} from '@src/editors/data/constants/problem';
+import ProblemTypeSelect from './content/ProblemTypeSelect';
+import Preview from './content/Preview';
+import AdvanceTypeSelect from './content/AdvanceTypeSelect';
+import SelectTypeWrapper from './SelectTypeWrapper';
+import * as hooks from './hooks';
interface Props {
onClose: (() => void) | null;
diff --git a/src/editors/data/constants/problem.test.ts b/src/editors/data/constants/problem.test.ts
new file mode 100644
index 000000000..e11f855e6
--- /dev/null
+++ b/src/editors/data/constants/problem.test.ts
@@ -0,0 +1,13 @@
+import { getProblemTitles } from '@src/editors/data/constants/problem';
+
+describe('getProblemTitles', () => {
+ it('returns a set of localized problem titles', () => {
+ const formatMessage = (msg) => msg.id || msg.defaultMessage || String(msg);
+ const titles = getProblemTitles(formatMessage);
+
+ expect(titles.size).toBeGreaterThan(0);
+ for (const title of titles) {
+ expect(typeof title).toBe('string');
+ }
+ });
+});
diff --git a/src/editors/data/constants/problem.ts b/src/editors/data/constants/problem.ts
index 1fd3bd908..804dba3b5 100644
--- a/src/editors/data/constants/problem.ts
+++ b/src/editors/data/constants/problem.ts
@@ -6,6 +6,7 @@ import numericalInput from '../images/numericalInput.png';
import textInput from '../images/textInput.png';
import advancedOlxTemplates from './advancedOlxTemplates';
import basicProblemTemplates from './basicProblemTemplates';
+import problemMessages from '../../containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/messages';
export const ProblemTypeKeys = StrictDict({
SINGLESELECT: 'multiplechoiceresponse',
@@ -17,6 +18,87 @@ export const ProblemTypeKeys = StrictDict({
} as const);
export type ProblemType = typeof ProblemTypeKeys[keyof typeof ProblemTypeKeys];
+/**
+ * Get problem types with internationalized strings.
+ * @param {Function} formatMessage - The intl.formatMessage function
+ * @returns {Object} ProblemTypes object with localized strings
+ *
+ * Usage in React components:
+ *
+ * import { useIntl } from '@edx/frontend-platform/i18n';
+ * import { getProblemTypes } from '../path/to/problem';
+ *
+ * const MyComponent = () => {
+ * const intl = useIntl();
+ * const localizedProblemTypes = getProblemTypes(intl.formatMessage);
+ *
+ * return {localizedProblemTypes[ProblemTypeKeys.SINGLESELECT].title}
;
+ * };
+ */
+export const getProblemTypes = (formatMessage) => ({
+ [ProblemTypeKeys.SINGLESELECT]: {
+ title: formatMessage(problemMessages.singleSelectTitle),
+ preview: singleSelect,
+ previewDescription: formatMessage(problemMessages.singleSelectDescription),
+ description: formatMessage(problemMessages.singleSelectInstruction),
+ helpLink: 'https://docs.openedx.org/en/latest/educators/concepts/exercise_tools/about_multi_select.html',
+ prev: ProblemTypeKeys.TEXTINPUT,
+ next: ProblemTypeKeys.MULTISELECT,
+ template: basicProblemTemplates.singleSelect.olx,
+ markdownTemplate: basicProblemTemplates.singleSelect.markdown,
+ },
+ [ProblemTypeKeys.MULTISELECT]: {
+ title: formatMessage(problemMessages.multiSelectTitle),
+ preview: multiSelect,
+ previewDescription: formatMessage(problemMessages.multiSelectDescription),
+ description: formatMessage(problemMessages.multiSelectInstruction),
+ helpLink: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/add_multi_select.html',
+ next: ProblemTypeKeys.DROPDOWN,
+ prev: ProblemTypeKeys.SINGLESELECT,
+ template: basicProblemTemplates.multiSelect.olx,
+ markdownTemplate: basicProblemTemplates.multiSelect.markdown,
+ },
+ [ProblemTypeKeys.DROPDOWN]: {
+ title: formatMessage(problemMessages.dropdownTitle),
+ preview: dropdown,
+ previewDescription: formatMessage(problemMessages.dropdownDescription),
+ description: formatMessage(problemMessages.dropdownInstruction),
+ helpLink: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/add_dropdown.html',
+ next: ProblemTypeKeys.NUMERIC,
+ prev: ProblemTypeKeys.MULTISELECT,
+ template: basicProblemTemplates.dropdown.olx,
+ markdownTemplate: basicProblemTemplates.dropdown.markdown,
+ },
+ [ProblemTypeKeys.NUMERIC]: {
+ title: formatMessage(problemMessages.numericalInputTitle),
+ preview: numericalInput,
+ previewDescription: formatMessage(problemMessages.numericalInputDescription),
+ description: formatMessage(problemMessages.numericalInputInstruction),
+ helpLink: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/manage_numerical_input_problem.html',
+ next: ProblemTypeKeys.TEXTINPUT,
+ prev: ProblemTypeKeys.DROPDOWN,
+ template: basicProblemTemplates.numeric.olx,
+ markdownTemplate: basicProblemTemplates.numeric.markdown,
+ },
+ [ProblemTypeKeys.TEXTINPUT]: {
+ title: formatMessage(problemMessages.textInputTitle),
+ preview: textInput,
+ previewDescription: formatMessage(problemMessages.textInputDescription),
+ description: formatMessage(problemMessages.textInputInstruction),
+ helpLink: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/add_text_input.html',
+ prev: ProblemTypeKeys.NUMERIC,
+ next: ProblemTypeKeys.SINGLESELECT,
+ template: basicProblemTemplates.textInput.olx,
+ markdownTemplate: basicProblemTemplates.textInput.markdown,
+ },
+ [ProblemTypeKeys.ADVANCED]: {
+ title: formatMessage(problemMessages.advancedProblemTitle),
+ preview: (''),
+ description: formatMessage(problemMessages.advancedProblemDescription),
+ helpLink: 'something.com',
+ },
+});
+
export const ProblemTypes = StrictDict({
[ProblemTypeKeys.SINGLESELECT]: {
title: 'Single select',
@@ -96,6 +178,61 @@ export function isAdvancedProblemType(pt: ProblemType | AdvancedProblemType): pt
return Object.values(AdvanceProblemKeys).includes(pt as any);
}
+/**
+ * Get advanced problem types with internationalized strings.
+ * @param {Function} formatMessage - The intl.formatMessage function
+ * @returns {Object} AdvanceProblems object with localized strings
+ *
+ * Usage in React components:
+ *
+ * import { useIntl } from '@edx/frontend-platform/i18n';
+ * import { getAdvanceProblems } from '../path/to/problem';
+ *
+ * const MyComponent = () => {
+ * const intl = useIntl();
+ * const localizedAdvanceProblems = getAdvanceProblems(intl.formatMessage);
+ *
+ * return {localizedAdvanceProblems[AdvanceProblemKeys.BLANK].title}
;
+ * };
+ */
+export const getAdvanceProblems = (formatMessage) => ({
+ [AdvanceProblemKeys.BLANK]: {
+ title: formatMessage(problemMessages.blankProblemTitle),
+ status: '',
+ template: '',
+ },
+ [AdvanceProblemKeys.CIRCUITSCHEMATIC]: {
+ title: formatMessage(problemMessages.circuitSchematicTitle),
+ status: 'Not supported',
+ template: advancedOlxTemplates.circuitSchematic,
+ },
+ [AdvanceProblemKeys.JSINPUT]: {
+ title: formatMessage(problemMessages.customJavaScriptTitle),
+ status: '',
+ template: advancedOlxTemplates.jsInputResponse,
+ },
+ [AdvanceProblemKeys.CUSTOMGRADER]: {
+ title: formatMessage(problemMessages.customPythonTitle),
+ status: 'Provisional',
+ template: advancedOlxTemplates.customGrader,
+ },
+ [AdvanceProblemKeys.IMAGE]: {
+ title: formatMessage(problemMessages.imageMappedTitle),
+ status: 'Not supported',
+ template: advancedOlxTemplates.imageResponse,
+ },
+ [AdvanceProblemKeys.FORMULA]: {
+ title: formatMessage(problemMessages.mathExpressionTitle),
+ status: '',
+ template: advancedOlxTemplates.formulaResponse,
+ },
+ [AdvanceProblemKeys.PROBLEMWITHHINT]: {
+ title: formatMessage(problemMessages.problemWithHintTitle),
+ status: 'Not supported',
+ template: advancedOlxTemplates.problemWithHint,
+ },
+});
+
export const AdvanceProblems = StrictDict({
[AdvanceProblemKeys.BLANK]: {
title: 'Blank problem',
@@ -248,3 +385,13 @@ export const ignoredOlxAttributes = [
// Useful for the block creation workflow.
export const problemTitles = new Set([...Object.values(ProblemTypes).map((problem) => problem.title),
...Object.values(AdvanceProblems).map((problem) => problem.title)]);
+
+/**
+ * Get problem titles with internationalization support
+ * @param {Function} formatMessage - The intl.formatMessage function
+ * @returns {Set} Set of localized problem titles
+ */
+export const getProblemTitles = (formatMessage) => new Set([
+ ...Object.values(getProblemTypes(formatMessage)).map((problem) => problem.title),
+ ...Object.values(getAdvanceProblems(formatMessage)).map((problem) => problem.title),
+]);