From 9f3b5279f0064142bf5bfc54c7d64cf544024fbe Mon Sep 17 00:00:00 2001 From: Ihor Romaniuk Date: Mon, 5 Feb 2024 18:28:38 +0100 Subject: [PATCH] feat: [FC-0044] Unit page - Manage access modal (unit & xblocks) --- src/constants.js | 7 + src/course-outline/CourseOutline.jsx | 12 +- src/course-outline/CourseOutline.scss | 1 - src/course-outline/CourseOutline.test.jsx | 5 +- src/course-unit/CourseUnit.jsx | 10 +- src/course-unit/CourseUnit.test.jsx | 79 ++++- .../__mocks__/courseVerticalChildren.js | 136 ++++++++- .../course-xblock/CourseXBlock.jsx | 58 +++- .../course-xblock/CourseXBlock.scss | 30 +- .../course-xblock/CourseXBlock.test.jsx | 186 +++++++++++- src/course-unit/course-xblock/constants.js | 5 + src/course-unit/course-xblock/messages.js | 8 + .../xblock-messages/XBlockMessages.jsx | 49 +++ .../xblock-messages/XBlockMessages.test.jsx | 55 ++++ .../course-xblock/xblock-messages/utils.js | 16 + .../xblock-messages/utils.test.js | 44 +++ src/course-unit/data/api.js | 8 +- src/course-unit/data/slice.js | 2 +- src/course-unit/data/thunk.js | 12 +- src/course-unit/data/utils.js | 5 +- src/course-unit/header-title/HeaderTitle.jsx | 99 +++--- .../header-title/HeaderTitle.test.jsx | 47 +++ src/course-unit/header-title/messages.js | 8 + src/course-unit/hooks.jsx | 8 + .../configure-modal/AdvancedTab.jsx | 0 .../configure-modal/BasicTab.jsx | 4 +- .../configure-modal/ConfigureModal.jsx | 80 ++++- .../configure-modal/ConfigureModal.scss | 0 .../configure-modal/ConfigureModal.test.jsx | 287 +++++------------- .../configure-modal/PrereqSettings.jsx | 2 +- .../configure-modal/UnitTab.jsx | 58 +++- .../configure-modal/VisibilityTab.jsx | 2 +- .../configure-modal/__mocks__/index.js | 199 ++++++++++++ .../configure-modal/messages.js | 30 +- src/generic/styles.scss | 1 + src/i18n/messages/ar.json | 6 +- src/i18n/messages/de.json | 88 +++++- src/i18n/messages/de_DE.json | 73 ++++- src/i18n/messages/es_419.json | 88 +++++- src/i18n/messages/fa_IR.json | 88 +++++- src/i18n/messages/fr.json | 88 +++++- src/i18n/messages/fr_CA.json | 88 +++++- src/i18n/messages/hi.json | 88 +++++- src/i18n/messages/it.json | 88 +++++- src/i18n/messages/it_IT.json | 88 +++++- src/i18n/messages/pt.json | 88 +++++- src/i18n/messages/pt_PT.json | 88 +++++- src/i18n/messages/ru.json | 88 +++++- src/i18n/messages/uk.json | 88 +++++- src/i18n/messages/zh_CN.json | 88 +++++- 50 files changed, 2426 insertions(+), 350 deletions(-) create mode 100644 src/course-unit/course-xblock/constants.js create mode 100644 src/course-unit/course-xblock/xblock-messages/XBlockMessages.jsx create mode 100644 src/course-unit/course-xblock/xblock-messages/XBlockMessages.test.jsx create mode 100644 src/course-unit/course-xblock/xblock-messages/utils.js create mode 100644 src/course-unit/course-xblock/xblock-messages/utils.test.js rename src/{course-outline => generic}/configure-modal/AdvancedTab.jsx (100%) rename src/{course-outline => generic}/configure-modal/BasicTab.jsx (96%) rename src/{course-outline => generic}/configure-modal/ConfigureModal.jsx (77%) rename src/{course-outline => generic}/configure-modal/ConfigureModal.scss (100%) rename src/{course-outline => generic}/configure-modal/ConfigureModal.test.jsx (56%) rename src/{course-outline => generic}/configure-modal/PrereqSettings.jsx (98%) rename src/{course-outline => generic}/configure-modal/UnitTab.jsx (70%) rename src/{course-outline => generic}/configure-modal/VisibilityTab.jsx (98%) create mode 100644 src/generic/configure-modal/__mocks__/index.js rename src/{course-outline => generic}/configure-modal/messages.js (94%) diff --git a/src/constants.js b/src/constants.js index 2913884a94..93c07d4c65 100644 --- a/src/constants.js +++ b/src/constants.js @@ -49,3 +49,10 @@ export const DECODED_ROUTES = { '/container/:blockId', ], }; + +export const COURSE_BLOCK_NAMES = /** @type {const} */ ({ + chapter: { id: 'chapter', name: 'Section' }, + sequential: { id: 'sequential', name: 'Subsection' }, + vertical: { id: 'vertical', name: 'Unit' }, + component: { id: 'component', name: 'Component' }, +}); diff --git a/src/course-outline/CourseOutline.jsx b/src/course-outline/CourseOutline.jsx index 07d11a9f08..40c7820130 100644 --- a/src/course-outline/CourseOutline.jsx +++ b/src/course-outline/CourseOutline.jsx @@ -25,9 +25,10 @@ import SubHeader from '../generic/sub-header/SubHeader'; import ProcessingNotification from '../generic/processing-notification'; import InternetConnectionAlert from '../generic/internet-connection-alert'; import DeleteModal from '../generic/delete-modal/DeleteModal'; +import ConfigureModal from '../generic/configure-modal/ConfigureModal'; import AlertMessage from '../generic/alert-message'; import getPageHeadTitle from '../generic/utils'; -import { getCurrentItem } from './data/selectors'; +import { getCurrentItem, getProctoredExamsFlag } from './data/selectors'; import { COURSE_BLOCK_NAMES } from './constants'; import HeaderNavigations from './header-navigations/HeaderNavigations'; import OutlineSideBar from './outline-sidebar/OutlineSidebar'; @@ -39,7 +40,6 @@ import UnitCard from './unit-card/UnitCard'; import HighlightsModal from './highlights-modal/HighlightsModal'; import EmptyPlaceholder from './empty-placeholder/EmptyPlaceholder'; import PublishModal from './publish-modal/PublishModal'; -import ConfigureModal from './configure-modal/ConfigureModal'; import PageAlerts from './page-alerts/PageAlerts'; import { useCourseOutline } from './hooks'; import messages from './messages'; @@ -118,8 +118,10 @@ const CourseOutline = ({ courseId }) => { title: processingNotificationTitle, } = useSelector(getProcessingNotification); - const { category } = useSelector(getCurrentItem); - const deleteCategory = COURSE_BLOCK_NAMES[category]?.name.toLowerCase(); + const currentItemData = useSelector(getCurrentItem); + const deleteCategory = COURSE_BLOCK_NAMES[currentItemData.category]?.name.toLowerCase(); + + const enableProctoredExams = useSelector(getProctoredExamsFlag); const finalizeSectionOrder = () => (newSections) => { initialSections = [...sectionsList]; @@ -485,6 +487,8 @@ const CourseOutline = ({ courseId }) => { isOpen={isConfigureModalOpen} onClose={handleConfigureModalClose} onConfigureSubmit={handleConfigureItemSubmit} + currentItemData={currentItemData} + enableProctoredExams={enableProctoredExams} /> { handleTitleEdit, handleInternetConnectionFailed, handleCreateNewCourseXBlock, + handleConfigureSubmit, courseVerticalChildren, } = useCourseUnit({ courseId, blockId }); @@ -82,6 +83,7 @@ const CourseUnit = ({ courseId }) => { isTitleEditFormOpen={isTitleEditFormOpen} handleTitleEdit={handleTitleEdit} handleTitleEditSubmit={handleTitleEditSubmit} + handleConfigureSubmit={handleConfigureSubmit} /> )} breadcrumbs={( @@ -115,14 +117,20 @@ const CourseUnit = ({ courseId }) => { /> )} - {courseVerticalChildren.children.map(({ name, blockId: id, shouldScroll }) => ( + {courseVerticalChildren.children.map(({ + name, blockId: id, shouldScroll, userPartitionInfo, validationMessages, + }) => ( ))} diff --git a/src/course-unit/CourseUnit.test.jsx b/src/course-unit/CourseUnit.test.jsx index a4d4337bb5..ce0fffb064 100644 --- a/src/course-unit/CourseUnit.test.jsx +++ b/src/course-unit/CourseUnit.test.jsx @@ -38,12 +38,13 @@ import courseSequenceMessages from './course-sequence/messages'; import sidebarMessages from './sidebar/messages'; import { extractCourseUnitId } from './sidebar/utils'; import CourseUnit from './CourseUnit'; -import messages from './messages'; import deleteModalMessages from '../generic/delete-modal/messages'; +import configureModalMessages from '../generic/configure-modal/messages'; import courseXBlockMessages from './course-xblock/messages'; import addComponentMessages from './add-component/messages'; import { PUBLISH_TYPES, UNIT_VISIBILITY_STATES } from './constants'; +import messages from './messages'; let axiosMock; let store; @@ -539,6 +540,7 @@ describe('', () => { name: 'New Cloned XBlock', block_id: '1234567890', block_type: 'drag-and-drop-v2', + user_partition_info: {}, }, ], }); @@ -562,7 +564,7 @@ describe('', () => { }); }); - it('should toggle visibility and update course unit state accordingly', async () => { + it('should toggle visibility from sidebar and update course unit state accordingly', async () => { const { getByRole, getByTestId } = render(); let courseUnitSidebar; let draftUnpublishedChangesHeading; @@ -585,7 +587,7 @@ describe('', () => { axiosMock .onPost(getXBlockBaseApiUrl(blockId), { publish: PUBLISH_TYPES.republish, - metadata: { visible_to_staff_only: true }, + metadata: { visible_to_staff_only: true, group_access: null }, }) .reply(200, { dummy: 'value' }); axiosMock @@ -622,7 +624,7 @@ describe('', () => { axiosMock .onPost(getXBlockBaseApiUrl(blockId), { publish: PUBLISH_TYPES.republish, - metadata: { visible_to_staff_only: null }, + metadata: { visible_to_staff_only: null, group_access: null }, }) .reply(200, { dummy: 'value' }); axiosMock @@ -910,4 +912,73 @@ describe('', () => { .replace('{sectionName}', courseUnitIndexMock.release_date_from), )).toBeInTheDocument(); }); + + it('should toggle visibility from header configure modal and update course unit state accordingly', async () => { + const { getByRole, getByTestId } = render(); + let courseUnitSidebar; + let sidebarVisibilityCheckbox; + let modalVisibilityCheckbox; + let configureModal; + let restrictAccessSelect; + + await waitFor(() => { + courseUnitSidebar = getByTestId('course-unit-sidebar'); + sidebarVisibilityCheckbox = within(courseUnitSidebar) + .getByLabelText(sidebarMessages.visibilityCheckboxTitle.defaultMessage); + expect(sidebarVisibilityCheckbox).not.toBeChecked(); + + const headerConfigureBtn = getByRole('button', { name: /settings/i }); + expect(headerConfigureBtn).toBeInTheDocument(); + + userEvent.click(headerConfigureBtn); + configureModal = getByTestId('configure-modal'); + restrictAccessSelect = within(configureModal) + .getByRole('combobox', { name: configureModalMessages.restrictAccessTo.defaultMessage }); + expect(within(configureModal) + .getByText(configureModalMessages.unitVisibility.defaultMessage)).toBeInTheDocument(); + expect(within(configureModal) + .getByText(configureModalMessages.restrictAccessTo.defaultMessage)).toBeInTheDocument(); + expect(restrictAccessSelect).toBeInTheDocument(); + expect(restrictAccessSelect).toHaveValue('-1'); + + modalVisibilityCheckbox = within(configureModal) + .getByRole('checkbox', { name: configureModalMessages.hideFromLearners.defaultMessage }); + expect(modalVisibilityCheckbox).not.toBeChecked(); + + userEvent.click(modalVisibilityCheckbox); + expect(modalVisibilityCheckbox).toBeChecked(); + + userEvent.selectOptions(restrictAccessSelect, '0'); + const [, group1Checkbox] = within(configureModal).getAllByRole('checkbox'); + + userEvent.click(group1Checkbox); + expect(group1Checkbox).toBeChecked(); + }); + + axiosMock + .onPost(getXBlockBaseApiUrl(courseUnitIndexMock.id), { + publish: null, + metadata: { visible_to_staff_only: true, group_access: { 50: [2] } }, + }) + .reply(200, { dummy: 'value' }); + axiosMock + .onGet(getCourseUnitApiUrl(blockId)) + .replyOnce(200, { + ...courseUnitIndexMock, + visibility_state: UNIT_VISIBILITY_STATES.staffOnly, + has_explicit_staff_lock: true, + }); + + const modalSaveBtn = within(configureModal) + .getByRole('button', { name: configureModalMessages.saveButton.defaultMessage }); + userEvent.click(modalSaveBtn); + + await waitFor(() => { + expect(sidebarVisibilityCheckbox).toBeChecked(); + expect(within(courseUnitSidebar) + .getByText(sidebarMessages.sidebarTitleVisibleToStaffOnly.defaultMessage)).toBeInTheDocument(); + expect(within(courseUnitSidebar) + .getByText(sidebarMessages.visibilityStaffOnlyTitle.defaultMessage)).toBeInTheDocument(); + }); + }); }); diff --git a/src/course-unit/__mocks__/courseVerticalChildren.js b/src/course-unit/__mocks__/courseVerticalChildren.js index d7cc9bf611..a6d8102dc5 100644 --- a/src/course-unit/__mocks__/courseVerticalChildren.js +++ b/src/course-unit/__mocks__/courseVerticalChildren.js @@ -2,14 +2,146 @@ module.exports = { children: [ { name: 'Discussion', - block_id: 'block-v1:OpenedX+L153+3T2023+type@discussion+block@fecd20842dd24f50bdc06643e791b013', + block_id: 'block-v1:OpenedX+L153+3T2023+type@discussion+block@5a28279f24344723a96b1268d3b7cfc0', block_type: 'discussion', + actions: { + can_copy: true, + can_duplicate: true, + can_move: true, + can_manage_access: true, + can_delete: true, + }, + user_partition_info: { + selectable_partitions: [ + { + id: 970807507, + name: 'Content Groups', + scheme: 'cohort', + groups: [ + { + id: 1959537066, + name: 'Group 1', + selected: false, + deleted: false, + }, + { + id: 108068059, + name: 'Group 2', + selected: false, + deleted: false, + }, + ], + }, + ], + selected_partition_index: -1, + selected_groups_label: '', + }, + user_partitions: [ + { + id: 970807507, + name: 'Content Groups', + scheme: 'cohort', + groups: [ + { + id: 1959537066, + name: 'Group 1', + selected: false, + deleted: false, + }, + { + id: 108068059, + name: 'Group 2', + selected: false, + deleted: false, + }, + ], + }, + { + id: 50, + name: 'Enrollment Track Groups', + scheme: 'enrollment_track', + groups: [ + { + id: 1, + name: 'Audit', + selected: false, + deleted: false, + }, + ], + }, + ], }, { name: 'Drag and Drop', block_id: 'block-v1:OpenedX+L153+3T2023+type@drag-and-drop-v2+block@b33cf1f6df4c41639659bc91132eeb02', block_type: 'drag-and-drop-v2', + actions: { + can_copy: true, + can_duplicate: true, + can_move: true, + can_manage_access: true, + can_delete: true, + }, + user_partition_info: { + selectable_partitions: [ + { + id: 970807507, + name: 'Content Groups', + scheme: 'cohort', + groups: [ + { + id: 1959537066, + name: 'Group 1', + selected: false, + deleted: false, + }, + { + id: 108068059, + name: 'Group 2', + selected: false, + deleted: false, + }, + ], + }, + ], + selected_partition_index: -1, + selected_groups_label: '', + }, + user_partitions: [ + { + id: 970807507, + name: 'Content Groups', + scheme: 'cohort', + groups: [ + { + id: 1959537066, + name: 'Group 1', + selected: false, + deleted: false, + }, + { + id: 108068059, + name: 'Group 2', + selected: false, + deleted: false, + }, + ], + }, + { + id: 50, + name: 'Enrollment Track Groups', + scheme: 'enrollment_track', + groups: [ + { + id: 1, + name: 'Audit', + selected: false, + deleted: false, + }, + ], + }, + ], }, ], - is_published: false, + isPublished: false, }; diff --git a/src/course-unit/course-xblock/CourseXBlock.jsx b/src/course-unit/course-xblock/CourseXBlock.jsx index bd8a11ebfc..8178f18a2b 100644 --- a/src/course-unit/course-xblock/CourseXBlock.jsx +++ b/src/course-unit/course-xblock/CourseXBlock.jsx @@ -7,21 +7,41 @@ import { EditOutline as EditIcon, MoreVert as MoveVertIcon } from '@openedx/para import { useIntl } from '@edx/frontend-platform/i18n'; import DeleteModal from '../../generic/delete-modal/DeleteModal'; +import ConfigureModal from '../../generic/configure-modal/ConfigureModal'; import { scrollToElement } from '../../course-outline/utils'; +import { COURSE_BLOCK_NAMES } from '../../constants'; +import XBlockMessages from './xblock-messages/XBlockMessages'; import messages from './messages'; const CourseXBlock = ({ - id, title, unitXBlockActions, shouldScroll, ...props + id, title, unitXBlockActions, shouldScroll, userPartitionInfo, + handleConfigureSubmit, validationMessages, ...props }) => { const courseXBlockElementRef = useRef(null); const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false); + const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false); const intl = useIntl(); - const onXBlockDelete = () => { + const visibilityMessage = userPartitionInfo.selectedGroupsLabel + ? intl.formatMessage(messages.visibilityMessage, { selectedGroupsLabel: userPartitionInfo.selectedGroupsLabel }) + : null; + + const currentItemData = { + category: COURSE_BLOCK_NAMES.component.id, + displayName: title, + userPartitionInfo, + showCorrectness: 'always', + }; + + const onDeleteSubmit = () => { unitXBlockActions.handleDelete(id); closeDeleteModal(); }; + const onConfigureSubmit = (...arg) => { + handleConfigureSubmit(id, ...arg, closeConfigureModal); + }; + useEffect(() => { // if this item has been newly added, scroll to it. if (courseXBlockElementRef.current && shouldScroll) { @@ -34,6 +54,7 @@ const CourseXBlock = ({ {intl.formatMessage(messages.blockLabelButtonMove)} - + {intl.formatMessage(messages.blockLabelButtonManageAccess)} @@ -73,13 +94,21 @@ const CourseXBlock = ({ category="component" isOpen={isDeleteModalOpen} close={closeDeleteModal} - onDeleteSubmit={onXBlockDelete} + onDeleteSubmit={onDeleteSubmit} + /> + )} size="md" /> +
@@ -88,6 +117,7 @@ const CourseXBlock = ({ }; CourseXBlock.defaultProps = { + validationMessages: [], shouldScroll: false, }; @@ -95,10 +125,30 @@ CourseXBlock.propTypes = { id: PropTypes.string.isRequired, title: PropTypes.string.isRequired, shouldScroll: PropTypes.bool, + validationMessages: PropTypes.arrayOf(PropTypes.shape({ + type: PropTypes.string, + text: PropTypes.string, + })), unitXBlockActions: PropTypes.shape({ handleDelete: PropTypes.func, handleDuplicate: PropTypes.func, }).isRequired, + userPartitionInfo: PropTypes.shape({ + selectablePartitions: PropTypes.arrayOf(PropTypes.shape({ + groups: PropTypes.arrayOf(PropTypes.shape({ + deleted: PropTypes.bool, + id: PropTypes.number, + name: PropTypes.string, + selected: PropTypes.bool, + })), + id: PropTypes.number, + name: PropTypes.string, + scheme: PropTypes.string, + })), + selectedPartitionIndex: PropTypes.number, + selectedGroupsLabel: PropTypes.string, + }).isRequired, + handleConfigureSubmit: PropTypes.func.isRequired, }; export default CourseXBlock; diff --git a/src/course-unit/course-xblock/CourseXBlock.scss b/src/course-unit/course-xblock/CourseXBlock.scss index 52c8e0bef5..0a042c8dca 100644 --- a/src/course-unit/course-xblock/CourseXBlock.scss +++ b/src/course-unit/course-xblock/CourseXBlock.scss @@ -1,15 +1,29 @@ .course-unit { - .pgn__card .pgn__card-header { - border-bottom: 1px solid $light-400; - padding-bottom: map-get($spacers, 2); + .course-unit__xblocks { + .pgn__card-header { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid $light-400; + padding-bottom: map-get($spacers, 2); + } + + .pgn__card-header-subtitle-md { + margin-top: 0; + font-size: 1rem; + } - .pgn__card-header-content { - margin-top: map-get($spacers, 3\.5); + .pgn__card-header-title-md { + font: 700 1.375rem/1.75rem $font-family-sans-serif; + color: $black; } - .btn-icon .btn-icon__icon { - width: 1.5rem; - height: 1.5rem; + .pgn__card-section { + padding: map-get($spacers, 3\.5) 0; } } + + .unit-iframe__wrapper .alert-danger { + margin-bottom: 0; + } } diff --git a/src/course-unit/course-xblock/CourseXBlock.test.jsx b/src/course-unit/course-xblock/CourseXBlock.test.jsx index 2c6defc766..c48213b856 100644 --- a/src/course-unit/course-xblock/CourseXBlock.test.jsx +++ b/src/course-unit/course-xblock/CourseXBlock.test.jsx @@ -1,37 +1,81 @@ -import { render, waitFor } from '@testing-library/react'; +import { + render, waitFor, within, +} from '@testing-library/react'; +import { useSelector } from 'react-redux'; import userEvent from '@testing-library/user-event'; import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { initializeMockApp } from '@edx/frontend-platform'; +import { camelCaseObject, initializeMockApp } from '@edx/frontend-platform'; import { AppProvider } from '@edx/frontend-platform/react'; +import MockAdapter from 'axios-mock-adapter'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { courseVerticalChildrenMock } from '../__mocks__'; -import CourseXBlock from './CourseXBlock'; - +import configureModalMessages from '../../generic/configure-modal/messages'; import deleteModalMessages from '../../generic/delete-modal/messages'; +import initializeStore from '../../store'; +import { getCourseSectionVerticalApiUrl, getXBlockBaseApiUrl } from '../data/api'; +import { fetchCourseSectionVerticalData } from '../data/thunk'; +import { executeThunk } from '../../utils'; +import { getCourseId } from '../data/selectors'; +import { PUBLISH_TYPES } from '../constants'; +import { courseSectionVerticalMock, courseVerticalChildrenMock } from '../__mocks__'; +import CourseXBlock from './CourseXBlock'; import messages from './messages'; +let axiosMock; let store; +const courseId = '1234'; +const blockId = '567890'; const handleDeleteMock = jest.fn(); const handleDuplicateMock = jest.fn(); -const xblockData = courseVerticalChildrenMock.children[0]; +const handleConfigureSubmitMock = jest.fn(); +const mockedUsedNavigate = jest.fn(); +const { + name, + block_id: id, + block_type: type, + user_partition_info: userPartitionInfo, +} = courseVerticalChildrenMock.children[0]; +const userPartitionInfoFormatted = camelCaseObject(userPartitionInfo); const unitXBlockActionsMock = { handleDelete: handleDeleteMock, handleDuplicate: handleDuplicateMock, }; -const renderComponent = () => render( +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockedUsedNavigate, +})); + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useSelector: jest.fn(), +})); + +const renderComponent = (props) => render( , ); +useSelector.mockImplementation((selector) => { + if (selector === getCourseId) { + return courseId; + } + return null; +}); + describe('', () => { beforeEach(async () => { initializeMockApp({ @@ -42,13 +86,20 @@ describe('', () => { roles: [], }, }); + + store = initializeStore(); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock + .onGet(getCourseSectionVerticalApiUrl(blockId)) + .reply(200, courseSectionVerticalMock); + await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch); }); it('render CourseXBlock component correctly', async () => { const { getByText, getByLabelText } = renderComponent(); await waitFor(() => { - expect(getByText(xblockData.block_id)).toBeInTheDocument(); + expect(getByText(name)).toBeInTheDocument(); expect(getByLabelText(messages.blockAltButtonEdit.defaultMessage)).toBeInTheDocument(); expect(getByLabelText(messages.blockActionsDropdownAlt.defaultMessage)).toBeInTheDocument(); }); @@ -59,7 +110,6 @@ describe('', () => { await waitFor(() => { userEvent.click(getByLabelText(messages.blockActionsDropdownAlt.defaultMessage)); - expect(getByRole('button', { name: messages.blockLabelButtonCopy.defaultMessage })).toBeInTheDocument(); expect(getByRole('button', { name: messages.blockLabelButtonDuplicate.defaultMessage })).toBeInTheDocument(); expect(getByRole('button', { name: messages.blockLabelButtonMove.defaultMessage })).toBeInTheDocument(); expect(getByRole('button', { name: messages.blockLabelButtonManageAccess.defaultMessage })).toBeInTheDocument(); @@ -76,7 +126,7 @@ describe('', () => { userEvent.click(duplicateBtn); expect(handleDuplicateMock).toHaveBeenCalledTimes(1); - expect(handleDuplicateMock).toHaveBeenCalledWith(xblockData.block_id); + expect(handleDuplicateMock).toHaveBeenCalledWith(id); }); }); @@ -102,7 +152,117 @@ describe('', () => { userEvent.click(deleteBtn); userEvent.click(getByRole('button', { name: deleteModalMessages.deleteButton.defaultMessage })); expect(handleDeleteMock).toHaveBeenCalled(); - expect(handleDeleteMock).toHaveBeenCalledWith(xblockData.block_id); + expect(handleDeleteMock).toHaveBeenCalledWith(id); + }); + }); + + describe('restrict access', () => { + it('opens restrict access modal successfully', async () => { + const { + getByText, + getByLabelText, + findByTestId, + } = renderComponent(); + + const modalSubtitleText = configureModalMessages.restrictAccessTo.defaultMessage; + const modalCancelBtnText = configureModalMessages.cancelButton.defaultMessage; + const modalSaveBtnText = configureModalMessages.saveButton.defaultMessage; + + userEvent.click(getByLabelText(messages.blockActionsDropdownAlt.defaultMessage)); + const accessBtn = getByText(messages.blockLabelButtonManageAccess.defaultMessage); + + userEvent.click(accessBtn); + const configureModal = await findByTestId('configure-modal'); + + expect(within(configureModal).getByText(modalSubtitleText)).toBeInTheDocument(); + expect(within(configureModal).getByRole('button', { name: modalCancelBtnText })).toBeInTheDocument(); + expect(within(configureModal).getByRole('button', { name: modalSaveBtnText })).toBeInTheDocument(); + }); + + it('closes restrict access modal when cancel button is clicked', async () => { + const { + getByText, + getByLabelText, + findByTestId, + } = renderComponent(); + + userEvent.click(getByLabelText(messages.blockActionsDropdownAlt.defaultMessage)); + const accessBtn = getByText(messages.blockLabelButtonManageAccess.defaultMessage); + + userEvent.click(accessBtn); + const configureModal = await findByTestId('configure-modal'); + expect(configureModal).toBeInTheDocument(); + + userEvent.click(within(configureModal).getByRole('button', { name: configureModalMessages.saveButton.defaultMessage })); + expect(handleConfigureSubmitMock).not.toHaveBeenCalled(); + }); + + it('handles submit restrict access data when save button is clicked', async () => { + axiosMock + .onPost(getXBlockBaseApiUrl(id), { + publish: PUBLISH_TYPES.republish, + metadata: { visible_to_staff_only: false, group_access: { 970807507: [1959537066] } }, + }) + .reply(200, { dummy: 'value' }); + + const { + getByText, + getByLabelText, + findByTestId, + getByRole, + } = renderComponent(); + const accessGroupName1 = userPartitionInfoFormatted.selectablePartitions[0].groups[0].name; + const accessGroupName2 = userPartitionInfoFormatted.selectablePartitions[0].groups[1].name; + + userEvent.click(getByLabelText(messages.blockActionsDropdownAlt.defaultMessage)); + const accessBtn = getByText(messages.blockLabelButtonManageAccess.defaultMessage); + + userEvent.click(accessBtn); + const configureModal = await findByTestId('configure-modal'); + expect(configureModal).toBeInTheDocument(); + expect(within(configureModal).queryByText(accessGroupName1)).not.toBeInTheDocument(); + expect(within(configureModal).queryByText(accessGroupName2)).not.toBeInTheDocument(); + + const restrictAccessSelect = getByRole('combobox', { + name: configureModalMessages.restrictAccessTo.defaultMessage, + }); + userEvent.selectOptions(restrictAccessSelect, '0'); + + // eslint-disable-next-line array-callback-return + userPartitionInfoFormatted.selectablePartitions[0].groups.map((group) => { + expect(within(configureModal).getByRole('checkbox', { name: group.name })).not.toBeChecked(); + expect(within(configureModal).queryByText(group.name)).toBeInTheDocument(); + }); + + const group1Checkbox = within(configureModal).getByRole('checkbox', { name: accessGroupName1 }); + userEvent.click(group1Checkbox); + expect(group1Checkbox).toBeChecked(); + + const saveModalBtnText = within(configureModal).getByRole('button', { + name: configureModalMessages.saveButton.defaultMessage, + }); + expect(saveModalBtnText).toBeInTheDocument(); + userEvent.click(saveModalBtnText); + await waitFor(() => { + expect(handleConfigureSubmitMock).toHaveBeenCalledTimes(1); + }); + }); + }); + + it('displays a visibility message if item has accessible restrictions', async () => { + const { getByText } = renderComponent( + { + userPartitionInfo: { + ...userPartitionInfoFormatted, + selectedGroupsLabel: 'Visibility group 1', + }, + }, + ); + + await waitFor(() => { + const visibilityMessage = messages.visibilityMessage.defaultMessage + .replace('{selectedGroupsLabel}', 'Visibility group 1'); + expect(getByText(visibilityMessage)).toBeInTheDocument(); }); }); }); diff --git a/src/course-unit/course-xblock/constants.js b/src/course-unit/course-xblock/constants.js new file mode 100644 index 0000000000..5f0177ce72 --- /dev/null +++ b/src/course-unit/course-xblock/constants.js @@ -0,0 +1,5 @@ +// eslint-disable-next-line import/prefer-default-export +export const MESSAGE_ERROR_TYPES = { + error: 'error', + warning: 'warning', +}; diff --git a/src/course-unit/course-xblock/messages.js b/src/course-unit/course-xblock/messages.js index e4b6365424..154485e15e 100644 --- a/src/course-unit/course-xblock/messages.js +++ b/src/course-unit/course-xblock/messages.js @@ -29,6 +29,14 @@ const messages = defineMessages({ id: 'course-authoring.course-unit.xblock.button.delete.label', defaultMessage: 'Delete', }, + visibilityMessage: { + id: 'course-authoring.course-unit.xblock.visibility.message', + defaultMessage: 'Access restricted to: {selectedGroupsLabel}', + }, + validationSummary: { + id: 'course-authoring.course-unit.xblock.validation.summary', + defaultMessage: 'This component has validation issues.', + }, }); export default messages; diff --git a/src/course-unit/course-xblock/xblock-messages/XBlockMessages.jsx b/src/course-unit/course-xblock/xblock-messages/XBlockMessages.jsx new file mode 100644 index 0000000000..0d7e32a4b1 --- /dev/null +++ b/src/course-unit/course-xblock/xblock-messages/XBlockMessages.jsx @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types'; +import { Alert } from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { Info as InfoIcon, WarningFilled as WarningIcon } from '@openedx/paragon/icons'; + +import messages from '../messages'; +import { MESSAGE_ERROR_TYPES } from '../constants'; +import { getMessagesBlockType } from './utils'; + +const XBlockMessages = ({ validationMessages }) => { + const intl = useIntl(); + const type = getMessagesBlockType(validationMessages); + const { warning } = MESSAGE_ERROR_TYPES; + const alertVariant = type === warning ? 'warning' : 'danger'; + const alertIcon = type === warning ? WarningIcon : InfoIcon; + + if (!validationMessages.length) { + return null; + } + + return ( + + + {intl.formatMessage(messages.validationSummary)} + +
    + {validationMessages.map(({ text }) => ( +
  • {text}
  • + ))} +
+
+ ); +}; + +XBlockMessages.defaultProps = { + validationMessages: [], +}; + +XBlockMessages.propTypes = { + validationMessages: PropTypes.arrayOf(PropTypes.shape({ + type: PropTypes.string, + text: PropTypes.string, + })), +}; + +export default XBlockMessages; diff --git a/src/course-unit/course-xblock/xblock-messages/XBlockMessages.test.jsx b/src/course-unit/course-xblock/xblock-messages/XBlockMessages.test.jsx new file mode 100644 index 0000000000..8d7e36e98a --- /dev/null +++ b/src/course-unit/course-xblock/xblock-messages/XBlockMessages.test.jsx @@ -0,0 +1,55 @@ +import { render } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; + +import messages from '../messages'; +import XBlockMessages from './XBlockMessages'; + +const renderComponent = (props) => render( + + + , +); + +describe('', () => { + it('renders without errors', () => { + renderComponent({ validationMessages: [] }); + }); + + it('does not render anything when there are no errors', () => { + const { container } = renderComponent({ validationMessages: [] }); + expect(container.firstChild).toBeNull(); + }); + + it('renders a warning Alert when there are warning errors', () => { + const validationMessages = [{ type: 'warning', text: 'This is a warning' }]; + const { getByText } = renderComponent({ validationMessages }); + + expect(getByText('This is a warning')).toBeInTheDocument(); + expect(getByText(messages.validationSummary.defaultMessage)).toBeInTheDocument(); + }); + + it('renders a danger Alert when there are danger errors', () => { + const validationMessages = [{ type: 'danger', text: 'This is a danger' }]; + const { getByText } = renderComponent({ validationMessages }); + + expect(getByText('This is a danger')).toBeInTheDocument(); + expect(getByText(messages.validationSummary.defaultMessage)).toBeInTheDocument(); + }); + + it('renders multiple error messages in a list', () => { + const validationMessages = [ + { type: 'warning', text: 'Warning 1' }, + { type: 'danger', text: 'Danger 1' }, + { type: 'danger', text: 'Danger 2' }, + ]; + const { getByText } = renderComponent({ validationMessages }); + + expect(getByText('Warning 1')).toBeInTheDocument(); + expect(getByText('Danger 1')).toBeInTheDocument(); + expect(getByText('Danger 2')).toBeInTheDocument(); + expect(getByText(messages.validationSummary.defaultMessage)).toBeInTheDocument(); + }); +}); diff --git a/src/course-unit/course-xblock/xblock-messages/utils.js b/src/course-unit/course-xblock/xblock-messages/utils.js new file mode 100644 index 0000000000..2a815b7aa2 --- /dev/null +++ b/src/course-unit/course-xblock/xblock-messages/utils.js @@ -0,0 +1,16 @@ +import { MESSAGE_ERROR_TYPES } from '../constants'; + +/** + * Determines the block type based on the types of messages in the given array. + * @param {Array} messages - An array of message objects. + * @param {Object[]} messages.type - The type of each message (e.g., MESSAGE_ERROR_TYPES.error). + * @returns {string} - The block type determined by the messages (e.g., 'warning' or 'error'). + */ +// eslint-disable-next-line import/prefer-default-export +export const getMessagesBlockType = (messages) => { + let type = MESSAGE_ERROR_TYPES.warning; + if (messages.some((message) => message.type === MESSAGE_ERROR_TYPES.error)) { + type = MESSAGE_ERROR_TYPES.error; + } + return type; +}; diff --git a/src/course-unit/course-xblock/xblock-messages/utils.test.js b/src/course-unit/course-xblock/xblock-messages/utils.test.js new file mode 100644 index 0000000000..32e8dde4f6 --- /dev/null +++ b/src/course-unit/course-xblock/xblock-messages/utils.test.js @@ -0,0 +1,44 @@ +import { MESSAGE_ERROR_TYPES } from '../constants'; +import { getMessagesBlockType } from './utils'; + +describe('xblock-messages utils', () => { + describe('getMessagesBlockType', () => { + it('returns "warning" when there are no error messages', () => { + const messages = [ + { type: MESSAGE_ERROR_TYPES.warning, text: 'This is a warning' }, + { type: MESSAGE_ERROR_TYPES.warning, text: 'Another warning' }, + ]; + const result = getMessagesBlockType(messages); + + expect(result).toBe(MESSAGE_ERROR_TYPES.warning); + }); + + it('returns "error" when there is at least one error message', () => { + const messages = [ + { type: MESSAGE_ERROR_TYPES.warning, text: 'This is a warning' }, + { type: MESSAGE_ERROR_TYPES.error, text: 'This is an error' }, + { type: MESSAGE_ERROR_TYPES.warning, text: 'Another warning' }, + ]; + const result = getMessagesBlockType(messages); + + expect(result).toBe(MESSAGE_ERROR_TYPES.error); + }); + + it('returns "error" when there are only error messages', () => { + const messages = [ + { type: MESSAGE_ERROR_TYPES.error, text: 'This is an error' }, + { type: MESSAGE_ERROR_TYPES.error, text: 'Another error' }, + ]; + const result = getMessagesBlockType(messages); + + expect(result).toBe(MESSAGE_ERROR_TYPES.error); + }); + + it('returns "warning" when there are no messages', () => { + const messages = []; + const result = getMessagesBlockType(messages); + + expect(result).toBe(MESSAGE_ERROR_TYPES.warning); + }); + }); +}); diff --git a/src/course-unit/data/api.js b/src/course-unit/data/api.js index 6520d1e1de..3ec12cef43 100644 --- a/src/course-unit/data/api.js +++ b/src/course-unit/data/api.js @@ -88,14 +88,16 @@ export async function createCourseXblock({ * @param {string} unitId - The ID of the course unit. * @param {string} type - The action type (e.g., PUBLISH_TYPES.discardChanges). * @param {boolean} isVisible - The visibility status for students. + * @param {boolean} groupAccess - Access group key set. * @returns {Promise} A promise that resolves with the response data. */ -export async function handleCourseUnitVisibilityAndData(unitId, type, isVisible) { +export async function handleCourseUnitVisibilityAndData(unitId, type, isVisible, groupAccess) { const body = { - publish: type, + publish: groupAccess ? null : type, ...(type === PUBLISH_TYPES.republish ? { metadata: { - visible_to_staff_only: isVisible, + visible_to_staff_only: isVisible ? true : null, + group_access: groupAccess || null, }, } : {}), }; diff --git a/src/course-unit/data/slice.js b/src/course-unit/data/slice.js index 0134fcb054..436a957b2b 100644 --- a/src/course-unit/data/slice.js +++ b/src/course-unit/data/slice.js @@ -16,7 +16,7 @@ const slice = createSlice({ }, unit: {}, courseSectionVertical: {}, - courseVerticalChildren: [], + courseVerticalChildren: {}, }, reducers: { fetchCourseItemSuccess: (state, { payload }) => { diff --git a/src/course-unit/data/thunk.js b/src/course-unit/data/thunk.js index e18a0cc6d6..6d63531881 100644 --- a/src/course-unit/data/thunk.js +++ b/src/course-unit/data/thunk.js @@ -111,19 +111,19 @@ export function editCourseItemQuery(itemId, displayName, sequenceId) { }; } -export function editCourseUnitVisibilityAndData(itemId, type, isVisible) { +export function editCourseUnitVisibilityAndData(itemId, type, isVisible, groupAccess, isModalView, blockId = itemId) { return async (dispatch) => { dispatch(updateSavingStatus({ status: RequestStatus.PENDING })); dispatch(updateQueryPendingStatus(true)); - const notificationMessage = getNotificationMessage(type, isVisible); - dispatch(showProcessingNotification(notificationMessage)); + const notification = getNotificationMessage(type, isVisible, isModalView); + dispatch(showProcessingNotification(notification)); try { - await handleCourseUnitVisibilityAndData(itemId, type, isVisible).then(async (result) => { + await handleCourseUnitVisibilityAndData(itemId, type, isVisible, groupAccess).then(async (result) => { if (result) { - const courseUnit = await getCourseUnitData(itemId); + const courseUnit = await getCourseUnitData(blockId); dispatch(fetchCourseItemSuccess(courseUnit)); - const courseVerticalChildrenData = await getCourseVerticalChildren(itemId); + const courseVerticalChildrenData = await getCourseVerticalChildren(blockId); dispatch(updateCourseVerticalChildren(courseVerticalChildrenData)); dispatch(hideProcessingNotification()); dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL })); diff --git a/src/course-unit/data/utils.js b/src/course-unit/data/utils.js index a37faaa4db..49223e1a7d 100644 --- a/src/course-unit/data/utils.js +++ b/src/course-unit/data/utils.js @@ -30,15 +30,18 @@ export function normalizeCourseSectionVerticalData(metadata) { * Get the notification message based on the publishing type and visibility. * @param {string} type - The publishing type. * @param {boolean} isVisible - The visibility status. + * @param {boolean} isModalView - The modal view status. * @returns {string} The corresponding notification message. */ -export const getNotificationMessage = (type, isVisible) => { +export const getNotificationMessage = (type, isVisible, isModalView) => { let notificationMessage; if (type === PUBLISH_TYPES.discardChanges) { notificationMessage = NOTIFICATION_MESSAGES.discardChanges; } else if (type === PUBLISH_TYPES.makePublic) { notificationMessage = NOTIFICATION_MESSAGES.publishing; + } else if (type === PUBLISH_TYPES.republish && isModalView) { + notificationMessage = NOTIFICATION_MESSAGES.saving; } else if (type === PUBLISH_TYPES.republish && !isVisible) { notificationMessage = NOTIFICATION_MESSAGES.makingVisibleToStudents; } else if (type === PUBLISH_TYPES.republish && isVisible) { diff --git a/src/course-unit/header-title/HeaderTitle.jsx b/src/course-unit/header-title/HeaderTitle.jsx index 4fc5739225..d2acd8044d 100644 --- a/src/course-unit/header-title/HeaderTitle.jsx +++ b/src/course-unit/header-title/HeaderTitle.jsx @@ -1,13 +1,15 @@ import { useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { Form, IconButton } from '@openedx/paragon'; +import { Form, IconButton, useToggle } from '@openedx/paragon'; import { EditOutline as EditIcon, Settings as SettingsIcon, } from '@openedx/paragon/icons'; +import ConfigureModal from '../../generic/configure-modal/ConfigureModal'; +import { getCourseUnitData } from '../data/selectors'; import { updateQueryPendingStatus } from '../data/slice'; import messages from './messages'; @@ -16,10 +18,30 @@ const HeaderTitle = ({ isTitleEditFormOpen, handleTitleEdit, handleTitleEditSubmit, + handleConfigureSubmit, }) => { const intl = useIntl(); const dispatch = useDispatch(); const [titleValue, setTitleValue] = useState(unitTitle); + const currentItemData = useSelector(getCourseUnitData); + const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false); + const { selectedPartitionIndex, selectedGroupsLabel } = currentItemData.userPartitionInfo; + + const onConfigureSubmit = (...arg) => { + handleConfigureSubmit(currentItemData.id, ...arg, closeConfigureModal); + }; + + const getVisibilityMessage = () => { + let message; + + if (selectedPartitionIndex !== -1 && !Number.isNaN(selectedPartitionIndex) && selectedGroupsLabel) { + message = intl.formatMessage(messages.definedVisibilityMessage, { selectedGroupsLabel }); + } else if (currentItemData.hasPartitionGroupComponents) { + message = intl.formatMessage(messages.commonVisibilityMessage); + } + + return message ? (

{message}

) : null; + }; useEffect(() => { setTitleValue(unitTitle); @@ -27,38 +49,46 @@ const HeaderTitle = ({ }, [unitTitle]); return ( -
- {isTitleEditFormOpen ? ( - - e && e.focus()} - value={titleValue} - name="displayName" - onChange={(e) => setTitleValue(e.target.value)} - aria-label={intl.formatMessage(messages.ariaLabelButtonEdit)} - onBlur={() => handleTitleEditSubmit(titleValue)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - handleTitleEditSubmit(titleValue); - } - }} - /> - - ) : unitTitle} - - { - }} - /> -
+ <> +
+ {isTitleEditFormOpen ? ( + + e && e.focus()} + value={titleValue} + name="displayName" + onChange={(e) => setTitleValue(e.target.value)} + aria-label={intl.formatMessage(messages.ariaLabelButtonEdit)} + onBlur={() => handleTitleEditSubmit(titleValue)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handleTitleEditSubmit(titleValue); + } + }} + /> + + ) : unitTitle} + + + +
+ {getVisibilityMessage()} + ); }; @@ -67,6 +97,7 @@ HeaderTitle.propTypes = { isTitleEditFormOpen: PropTypes.bool.isRequired, handleTitleEdit: PropTypes.func.isRequired, handleTitleEditSubmit: PropTypes.func.isRequired, + handleConfigureSubmit: PropTypes.func.isRequired, }; export default HeaderTitle; diff --git a/src/course-unit/header-title/HeaderTitle.test.jsx b/src/course-unit/header-title/HeaderTitle.test.jsx index b5014f89c2..7e57c408e0 100644 --- a/src/course-unit/header-title/HeaderTitle.test.jsx +++ b/src/course-unit/header-title/HeaderTitle.test.jsx @@ -1,3 +1,5 @@ +import MockAdapter from 'axios-mock-adapter'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { IntlProvider } from '@edx/frontend-platform/i18n'; @@ -5,14 +7,21 @@ import { AppProvider } from '@edx/frontend-platform/react'; import { initializeMockApp } from '@edx/frontend-platform'; import initializeStore from '../../store'; +import { executeThunk } from '../../utils'; +import { getCourseUnitApiUrl } from '../data/api'; +import { fetchCourseUnitQuery } from '../data/thunk'; +import { courseUnitIndexMock } from '../__mocks__'; import HeaderTitle from './HeaderTitle'; import messages from './messages'; +const blockId = '123'; const unitTitle = 'Getting Started'; const isTitleEditFormOpen = false; const handleTitleEdit = jest.fn(); const handleTitleEditSubmit = jest.fn(); +const handleConfigureSubmit = jest.fn(); let store; +let axiosMock; const renderComponent = (props) => render( @@ -22,6 +31,7 @@ const renderComponent = (props) => render( isTitleEditFormOpen={isTitleEditFormOpen} handleTitleEdit={handleTitleEdit} handleTitleEditSubmit={handleTitleEditSubmit} + handleConfigureSubmit={handleConfigureSubmit} {...props} /> @@ -40,6 +50,11 @@ describe('', () => { }); store = initializeStore(); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + axiosMock + .onGet(getCourseUnitApiUrl(blockId)) + .reply(200, courseUnitIndexMock); + await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch); }); it('render HeaderTitle component correctly', () => { @@ -85,4 +100,36 @@ describe('', () => { expect(titleField).toHaveValue(`${unitTitle} 1 2`); expect(handleTitleEditSubmit).toHaveBeenCalledTimes(2); }); + + it('displays a visibility message with the selected groups for the unit', async () => { + axiosMock + .onGet(getCourseUnitApiUrl(blockId)) + .reply(200, { + ...courseUnitIndexMock, + user_partition_info: { + ...courseUnitIndexMock.user_partition_info, + selected_partition_index: '1', + selected_groups_label: 'Visibility group 1', + }, + }); + await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch); + const { getByText } = renderComponent(); + const visibilityMessage = messages.definedVisibilityMessage.defaultMessage + .replace('{selectedGroupsLabel}', 'Visibility group 1'); + + expect(getByText(visibilityMessage)).toBeInTheDocument(); + }); + + it('displays a visibility message with the selected groups for some of xblock', async () => { + axiosMock + .onGet(getCourseUnitApiUrl(blockId)) + .reply(200, { + ...courseUnitIndexMock, + has_partition_group_components: true, + }); + await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch); + const { getByText } = renderComponent(); + + expect(getByText(messages.commonVisibilityMessage.defaultMessage)).toBeInTheDocument(); + }); }); diff --git a/src/course-unit/header-title/messages.js b/src/course-unit/header-title/messages.js index c6ca9ef208..2313319388 100644 --- a/src/course-unit/header-title/messages.js +++ b/src/course-unit/header-title/messages.js @@ -13,6 +13,14 @@ const messages = defineMessages({ id: 'course-authoring.course-unit.heading.button.settings.alt', defaultMessage: 'Settings', }, + definedVisibilityMessage: { + id: 'course-authoring.course-unit.heading.visibility.defined.message', + defaultMessage: 'Access to this unit is restricted to: {selectedGroupsLabel}', + }, + commonVisibilityMessage: { + id: 'course-authoring.course-unit.heading.visibility.common.message', + defaultMessage: 'Access to some content in this unit is restricted to specific groups of learners.', + }, }); export default messages; diff --git a/src/course-unit/hooks.jsx b/src/course-unit/hooks.jsx index d1d1edc7bc..a2b0726e71 100644 --- a/src/course-unit/hooks.jsx +++ b/src/course-unit/hooks.jsx @@ -11,6 +11,7 @@ import { fetchCourseVerticalChildrenData, deleteUnitItemQuery, duplicateUnitItemQuery, + editCourseUnitVisibilityAndData, } from './data/thunk'; import { getCourseSectionVertical, @@ -21,6 +22,7 @@ import { getSequenceStatus, } from './data/selectors'; import { changeEditTitleFormOpen, updateQueryPendingStatus } from './data/slice'; +import { PUBLISH_TYPES } from './constants'; // eslint-disable-next-line import/prefer-default-export export const useCourseUnit = ({ courseId, blockId }) => { @@ -59,6 +61,11 @@ export const useCourseUnit = ({ courseId, blockId }) => { dispatch(changeEditTitleFormOpen(!isTitleEditFormOpen)); }; + const handleConfigureSubmit = (id, isVisible, groupAccess, closeModalFn) => { + dispatch(editCourseUnitVisibilityAndData(id, PUBLISH_TYPES.republish, isVisible, groupAccess, true, blockId)); + closeModalFn(); + }; + const handleTitleEditSubmit = (displayName) => { if (unitTitle !== displayName) { dispatch(editCourseItemQuery(blockId, displayName, sequenceId)); @@ -121,6 +128,7 @@ export const useCourseUnit = ({ courseId, blockId }) => { handleTitleEdit, handleTitleEditSubmit, handleCreateNewCourseXBlock, + handleConfigureSubmit, courseVerticalChildren, }; }; diff --git a/src/course-outline/configure-modal/AdvancedTab.jsx b/src/generic/configure-modal/AdvancedTab.jsx similarity index 100% rename from src/course-outline/configure-modal/AdvancedTab.jsx rename to src/generic/configure-modal/AdvancedTab.jsx diff --git a/src/course-outline/configure-modal/BasicTab.jsx b/src/generic/configure-modal/BasicTab.jsx similarity index 96% rename from src/course-outline/configure-modal/BasicTab.jsx rename to src/generic/configure-modal/BasicTab.jsx index 173bc34939..182de34df1 100644 --- a/src/course-outline/configure-modal/BasicTab.jsx +++ b/src/generic/configure-modal/BasicTab.jsx @@ -1,9 +1,9 @@ -import React from 'react'; import PropTypes from 'prop-types'; import { Stack, Form } from '@openedx/paragon'; import { FormattedMessage, injectIntl, useIntl } from '@edx/frontend-platform/i18n'; + +import { DatepickerControl, DATEPICKER_TYPES } from '../datepicker-control'; import messages from './messages'; -import { DatepickerControl, DATEPICKER_TYPES } from '../../generic/datepicker-control'; const BasicTab = ({ values, diff --git a/src/course-outline/configure-modal/ConfigureModal.jsx b/src/generic/configure-modal/ConfigureModal.jsx similarity index 77% rename from src/course-outline/configure-modal/ConfigureModal.jsx rename to src/generic/configure-modal/ConfigureModal.jsx index 3e7556b466..a78bd386a7 100644 --- a/src/course-outline/configure-modal/ConfigureModal.jsx +++ b/src/generic/configure-modal/ConfigureModal.jsx @@ -11,12 +11,10 @@ import { Tab, Tabs, } from '@openedx/paragon'; -import { useSelector } from 'react-redux'; import { Formik } from 'formik'; import { VisibilityTypes } from '../../data/constants'; -import { COURSE_BLOCK_NAMES } from '../constants'; -import { getCurrentItem, getProctoredExamsFlag } from '../data/selectors'; +import { COURSE_BLOCK_NAMES } from '../../constants'; import messages from './messages'; import BasicTab from './BasicTab'; import VisibilityTab from './VisibilityTab'; @@ -27,6 +25,9 @@ const ConfigureModal = ({ isOpen, onClose, onConfigureSubmit, + currentItemData, + enableProctoredExams, + isXBlockComponent, }) => { const intl = useIntl(); const { @@ -57,8 +58,7 @@ const ConfigureModal = ({ supportsOnboarding, showReviewRules, onlineProctoringRules, - } = useSelector(getCurrentItem); - const enableProctoredExams = useSelector(getProctoredExamsFlag); + } = currentItemData; const getSelectedGroups = () => { if (userPartitionInfo?.selectedPartitionIndex >= 0) { @@ -81,7 +81,6 @@ const ConfigureModal = ({ const initialValues = { releaseDate: sectionStartDate, isVisibleToStaffOnly: visibilityState === VisibilityTypes.STAFF_ONLY, - saveButtonDisabled: true, graderType: format == null ? 'notgraded' : format, dueDate: due == null ? '' : due, isTimeLimited, @@ -132,6 +131,10 @@ const ConfigureModal = ({ const isSubsection = category === COURSE_BLOCK_NAMES.sequential.id; + const dialogTitle = isXBlockComponent + ? intl.formatMessage(messages.componentTitle, { title: displayName }) + : intl.formatMessage(messages.title, { title: displayName }); + const handleSave = (data) => { const groupAccess = {}; switch (category) { @@ -159,6 +162,7 @@ const ConfigureModal = ({ ); break; case COURSE_BLOCK_NAMES.vertical.id: + case COURSE_BLOCK_NAMES.component.id: // groupAccess should be {partitionId: [group1, group2]} or {} if selectedPartitionIndex === -1 if (data.selectedPartitionIndex >= 0) { const partitionId = userPartitionInfo.selectablePartitions[data.selectedPartitionIndex].id; @@ -232,8 +236,10 @@ const ConfigureModal = ({ ); case COURSE_BLOCK_NAMES.vertical.id: + case COURSE_BLOCK_NAMES.component.id: return ( - {intl.formatMessage(messages.title, { title: displayName })} + {dialogTitle} {({ - values, handleSubmit, dirty, isValid, setFieldValue, + values, handleSubmit, setFieldValue, }) => ( <> @@ -281,7 +287,10 @@ const ConfigureModal = ({ {intl.formatMessage(messages.cancelButton)} - @@ -294,10 +303,63 @@ const ConfigureModal = ({ ); }; +ConfigureModal.defaultProps = { + isXBlockComponent: false, + enableProctoredExams: false, +}; + ConfigureModal.propTypes = { isOpen: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, onConfigureSubmit: PropTypes.func.isRequired, + enableProctoredExams: PropTypes.bool, + currentItemData: PropTypes.shape({ + displayName: PropTypes.string, + start: PropTypes.string, + visibilityState: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), + due: PropTypes.string, + isTimeLimited: PropTypes.bool, + defaultTimeLimitMinutes: PropTypes.number, + hideAfterDue: PropTypes.bool, + showCorrectness: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), + courseGraders: PropTypes.arrayOf(PropTypes.string), + category: PropTypes.string, + format: PropTypes.string, + userPartitionInfo: PropTypes.shape({ + selectablePartitions: PropTypes.arrayOf(PropTypes.shape({ + groups: PropTypes.arrayOf(PropTypes.shape({ + deleted: PropTypes.bool, + id: PropTypes.number, + name: PropTypes.string, + selected: PropTypes.bool, + })), + id: PropTypes.number, + name: PropTypes.string, + scheme: PropTypes.string, + })), + selectedPartitionIndex: PropTypes.number, + selectedGroupsLabel: PropTypes.string, + }), + ancestorHasStaffLock: PropTypes.bool, + isPrereq: PropTypes.bool, + prereqs: PropTypes.arrayOf({ + blockDisplayName: PropTypes.string, + blockUsageKey: PropTypes.string, + }), + prereq: PropTypes.number, + prereqMinScore: PropTypes.number, + prereqMinCompletion: PropTypes.number, + releasedToStudents: PropTypes.bool, + wasExamEverLinkedWithExternal: PropTypes.bool, + isProctoredExam: PropTypes.bool, + isOnboardingExam: PropTypes.bool, + isPracticeExam: PropTypes.bool, + examReviewRules: PropTypes.string, + supportsOnboarding: PropTypes.bool, + showReviewRules: PropTypes.bool, + onlineProctoringRules: PropTypes.string, + }).isRequired, + isXBlockComponent: PropTypes.bool, }; export default ConfigureModal; diff --git a/src/course-outline/configure-modal/ConfigureModal.scss b/src/generic/configure-modal/ConfigureModal.scss similarity index 100% rename from src/course-outline/configure-modal/ConfigureModal.scss rename to src/generic/configure-modal/ConfigureModal.scss diff --git a/src/course-outline/configure-modal/ConfigureModal.test.jsx b/src/generic/configure-modal/ConfigureModal.test.jsx similarity index 56% rename from src/course-outline/configure-modal/ConfigureModal.test.jsx rename to src/generic/configure-modal/ConfigureModal.test.jsx index 9756f32467..3c4d699446 100644 --- a/src/course-outline/configure-modal/ConfigureModal.test.jsx +++ b/src/generic/configure-modal/ConfigureModal.test.jsx @@ -1,7 +1,6 @@ -import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; +import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { useSelector } from 'react-redux'; import { initializeMockApp } from '@edx/frontend-platform'; import MockAdapter from 'axios-mock-adapter'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; @@ -9,6 +8,12 @@ import { AppProvider } from '@edx/frontend-platform/react'; import initializeStore from '../../store'; import ConfigureModal from './ConfigureModal'; +import { + currentSectionMock, + currentSubsectionMock, + currentUnitMock, + currentXBlockMock, +} from './__mocks__'; import messages from './messages'; // eslint-disable-next-line no-unused-vars @@ -28,79 +33,6 @@ jest.mock('react-router-dom', () => ({ }), })); -const currentSectionMock = { - displayName: 'Section1', - category: 'chapter', - start: '2025-08-10T10:00:00Z', - visibilityState: true, - format: 'Not Graded', - childInfo: { - displayName: 'Subsection', - children: [ - { - displayName: 'Subsection 1', - id: 1, - category: 'sequential', - due: '', - start: '2025-08-10T10:00:00Z', - visibilityState: true, - defaultTimeLimitMinutes: null, - hideAfterDue: false, - showCorrectness: false, - format: 'Homework', - courseGraders: ['Homework', 'Exam'], - childInfo: { - displayName: 'Unit', - children: [ - { - id: 11, - displayName: 'Subsection_1 Unit 1', - }, - ], - }, - }, - { - displayName: 'Subsection 2', - id: 2, - category: 'sequential', - due: '', - start: '2025-08-10T10:00:00Z', - visibilityState: true, - defaultTimeLimitMinutes: null, - hideAfterDue: false, - showCorrectness: false, - format: 'Homework', - courseGraders: ['Homework', 'Exam'], - childInfo: { - displayName: 'Unit', - children: [ - { - id: 21, - displayName: 'Subsection_2 Unit 1', - }, - ], - }, - }, - { - displayName: 'Subsection 3', - id: 3, - category: 'sequential', - due: '', - start: '2025-08-10T10:00:00Z', - visibilityState: true, - defaultTimeLimitMinutes: null, - hideAfterDue: false, - showCorrectness: false, - format: 'Homework', - courseGraders: ['Homework', 'Exam'], - childInfo: { - children: [], - }, - }, - ], - }, -}; - const onCloseMock = jest.fn(); const onConfigureSubmitMock = jest.fn(); @@ -111,6 +43,7 @@ const renderComponent = () => render( isOpen onClose={onCloseMock} onConfigureSubmit={onConfigureSubmitMock} + currentItemData={currentSectionMock} /> , , @@ -129,12 +62,11 @@ describe(' for Section', () => { store = initializeStore(); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); - useSelector.mockReturnValue(currentSectionMock); }); it('renders ConfigureModal component correctly', () => { const { getByText, getByRole } = renderComponent(); - expect(getByText(`${currentSectionMock.displayName} Settings`)).toBeInTheDocument(); + expect(getByText(`${currentSectionMock.displayName} settings`)).toBeInTheDocument(); expect(getByText(messages.basicTabTitle.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.visibilityTabTitle.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.releaseDate.defaultMessage)).toBeInTheDocument(); @@ -147,55 +79,12 @@ describe(' for Section', () => { const { getByRole, getByText } = renderComponent(); const visibilityTab = getByRole('tab', { name: messages.visibilityTabTitle.defaultMessage }); - fireEvent.click(visibilityTab); - expect(getByText('Section Visibility')).toBeInTheDocument(); + userEvent.click(visibilityTab); + expect(getByText('Section visibility')).toBeInTheDocument(); expect(getByText(messages.hideFromLearners.defaultMessage)).toBeInTheDocument(); }); - - it('disables the Save button and enables it if there is a change', () => { - const { getByRole, getByPlaceholderText, getByTestId } = renderComponent(); - - const saveButton = getByRole('button', { name: messages.saveButton.defaultMessage }); - expect(saveButton).toBeDisabled(); - - const input = getByPlaceholderText('MM/DD/YYYY'); - fireEvent.change(input, { target: { value: '12/15/2023' } }); - - const visibilityTab = getByRole('tab', { name: messages.visibilityTabTitle.defaultMessage }); - fireEvent.click(visibilityTab); - const checkbox = getByTestId('visibility-checkbox'); - fireEvent.click(checkbox); - expect(saveButton).not.toBeDisabled(); - }); }); -const currentSubsectionMock = { - displayName: 'Subsection 1', - id: 1, - category: 'sequential', - due: '', - start: '2025-08-10T10:00:00Z', - visibilityState: true, - defaultTimeLimitMinutes: null, - hideAfterDue: false, - showCorrectness: false, - format: 'Homework', - courseGraders: ['Homework', 'Exam'], - childInfo: { - displayName: 'Unit', - children: [ - { - id: 11, - displayName: 'Subsection_1 Unit 1', - }, - { - id: 12, - displayName: 'Subsection_1 Unit 2', - }, - ], - }, -}; - const renderSubsectionComponent = () => render( @@ -203,6 +92,7 @@ const renderSubsectionComponent = () => render( isOpen onClose={onCloseMock} onConfigureSubmit={onConfigureSubmitMock} + currentItemData={currentSubsectionMock} /> , , @@ -221,12 +111,11 @@ describe(' for Subsection', () => { store = initializeStore(); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); - useSelector.mockReturnValue(currentSubsectionMock); }); it('renders subsection ConfigureModal component correctly', () => { const { getByText, getByRole } = renderSubsectionComponent(); - expect(getByText(`${currentSubsectionMock.displayName} Settings`)).toBeInTheDocument(); + expect(getByText(`${currentSubsectionMock.displayName} settings`)).toBeInTheDocument(); expect(getByText(messages.basicTabTitle.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.visibilityTabTitle.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.advancedTabTitle.defaultMessage)).toBeInTheDocument(); @@ -244,8 +133,8 @@ describe(' for Subsection', () => { const { getByRole, getByText } = renderSubsectionComponent(); const visibilityTab = getByRole('tab', { name: messages.visibilityTabTitle.defaultMessage }); - fireEvent.click(visibilityTab); - expect(getByText('Subsection Visibility')).toBeInTheDocument(); + userEvent.click(visibilityTab); + expect(getByText('Subsection visibility')).toBeInTheDocument(); expect(getByText(messages.showEntireSubsection.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.showEntireSubsectionDescription.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.hideContentAfterDue.defaultMessage)).toBeInTheDocument(); @@ -265,82 +154,23 @@ describe(' for Subsection', () => { const { getByRole, getByText } = renderSubsectionComponent(); const advancedTab = getByRole('tab', { name: messages.advancedTabTitle.defaultMessage }); - fireEvent.click(advancedTab); + userEvent.click(advancedTab); expect(getByText(messages.setSpecialExam.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.none.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.timed.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.timedDescription.defaultMessage)).toBeInTheDocument(); }); - - it('disables the Save button and enables it if there is a change', () => { - const { getByRole, getByTestId } = renderSubsectionComponent(); - - const saveButton = getByRole('button', { name: messages.saveButton.defaultMessage }); - expect(saveButton).toBeDisabled(); - - const input = getByTestId('grader-type-select'); - fireEvent.change(input, { target: { value: 'Exam' } }); - expect(saveButton).not.toBeDisabled(); - }); }); -const currentUnitMock = { - displayName: 'Unit 1', - id: 1, - category: 'vertical', - due: '', - start: '2025-08-10T10:00:00Z', - visibilityState: true, - defaultTimeLimitMinutes: null, - hideAfterDue: false, - showCorrectness: false, - userPartitionInfo: { - selectablePartitions: [ - { - id: 50, - name: 'Enrollment Track Groups', - scheme: 'enrollment_track', - groups: [ - { - id: 6, - name: 'Honor', - selected: false, - deleted: false, - }, - { - id: 2, - name: 'Verified', - selected: false, - deleted: false, - }, - ], - }, - { - id: 1508065533, - name: 'Content Groups', - scheme: 'cohort', - groups: [ - { - id: 1224170703, - name: 'Content Group 1', - selected: false, - deleted: false, - }, - ], - }, - ], - selectedPartitionIndex: -1, - selectedGroupsLabel: '', - }, -}; - -const renderUnitComponent = () => render( +const renderUnitComponent = (props) => render( , , @@ -359,14 +189,13 @@ describe(' for Unit', () => { store = initializeStore(); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); - useSelector.mockReturnValue(currentUnitMock); }); it('renders unit ConfigureModal component correctly', () => { const { getByText, queryByText, getByRole, getByTestId, } = renderUnitComponent(); - expect(getByText(`${currentUnitMock.displayName} Settings`)).toBeInTheDocument(); + expect(getByText(`${currentUnitMock.displayName} settings`)).toBeInTheDocument(); expect(getByText(messages.unitVisibility.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.hideFromLearners.defaultMessage)).toBeInTheDocument(); expect(getByText(messages.restrictAccessTo.defaultMessage)).toBeInTheDocument(); @@ -375,8 +204,8 @@ describe(' for Unit', () => { expect(queryByText(messages.unitSelectGroup.defaultMessage)).not.toBeInTheDocument(); const input = getByTestId('group-type-select'); - [0, 1].forEach(groupeTypeIndex => { - fireEvent.change(input, { target: { value: groupeTypeIndex } }); + ['0', '1'].forEach(groupeTypeIndex => { + userEvent.selectOptions(input, groupeTypeIndex); expect(getByText(messages.unitSelectGroup.defaultMessage)).toBeInTheDocument(); currentUnitMock @@ -388,32 +217,62 @@ describe(' for Unit', () => { expect(getByRole('button', { name: messages.cancelButton.defaultMessage })).toBeInTheDocument(); expect(getByRole('button', { name: messages.saveButton.defaultMessage })).toBeInTheDocument(); }); +}); - it('disables the Save button and enables it if there is a change', () => { - useSelector.mockReturnValue( - { - ...currentUnitMock, - userPartitionInfo: { - ...currentUnitMock.userPartitionInfo, - selectedPartitionIndex: 0, - }, +const renderXBlockComponent = (props) => render( + + + + , + , +); + +describe(' for XBlock', () => { + beforeEach(() => { + initializeMockApp({ + authenticatedUser: { + userId: 3, + username: 'abc123', + administrator: true, + roles: [], }, - ); - const { getByRole, getByTestId } = renderUnitComponent(); + }); - const saveButton = getByRole('button', { name: messages.saveButton.defaultMessage }); - expect(saveButton).toBeDisabled(); + store = initializeStore(); + axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + }); + it('renders unit ConfigureModal component correctly', () => { + const { + getByText, queryByText, getByRole, getByTestId, + } = renderXBlockComponent(); + expect(getByText(`Editing access for: ${currentUnitMock.displayName}`)).toBeInTheDocument(); + expect(queryByText(messages.unitVisibility.defaultMessage)).not.toBeInTheDocument(); + expect(queryByText(messages.hideFromLearners.defaultMessage)).not.toBeInTheDocument(); + expect(getByText(messages.restrictAccessTo.defaultMessage)).toBeInTheDocument(); + expect(getByText(messages.unitSelectGroupType.defaultMessage)).toBeInTheDocument(); + + expect(queryByText(messages.unitSelectGroup.defaultMessage)).not.toBeInTheDocument(); const input = getByTestId('group-type-select'); - // unrestrict access - fireEvent.change(input, { target: { value: -1 } }); - expect(saveButton).not.toBeDisabled(); - fireEvent.change(input, { target: { value: 0 } }); - expect(saveButton).toBeDisabled(); + ['0', '1'].forEach(groupeTypeIndex => { + userEvent.selectOptions(input, groupeTypeIndex); + + expect(getByText(messages.unitSelectGroup.defaultMessage)).toBeInTheDocument(); + currentUnitMock + .userPartitionInfo + .selectablePartitions[groupeTypeIndex].groups + .forEach(g => expect(getByText(g.name)).toBeInTheDocument()); + }); - const checkbox = getByTestId('unit-visibility-checkbox'); - fireEvent.click(checkbox); - expect(saveButton).not.toBeDisabled(); + expect(getByRole('button', { name: messages.cancelButton.defaultMessage })).toBeInTheDocument(); + expect(getByRole('button', { name: messages.saveButton.defaultMessage })).toBeInTheDocument(); }); }); diff --git a/src/course-outline/configure-modal/PrereqSettings.jsx b/src/generic/configure-modal/PrereqSettings.jsx similarity index 98% rename from src/course-outline/configure-modal/PrereqSettings.jsx rename to src/generic/configure-modal/PrereqSettings.jsx index b79ffbf34d..74c5f7148e 100644 --- a/src/course-outline/configure-modal/PrereqSettings.jsx +++ b/src/generic/configure-modal/PrereqSettings.jsx @@ -4,7 +4,7 @@ import { Form } from '@openedx/paragon'; import { FormattedMessage, injectIntl, useIntl } from '@edx/frontend-platform/i18n'; import messages from './messages'; -import FormikControl from '../../generic/FormikControl'; +import FormikControl from '../FormikControl'; const PrereqSettings = ({ values, diff --git a/src/course-outline/configure-modal/UnitTab.jsx b/src/generic/configure-modal/UnitTab.jsx similarity index 70% rename from src/course-outline/configure-modal/UnitTab.jsx rename to src/generic/configure-modal/UnitTab.jsx index ec838711da..2c38ab17d0 100644 --- a/src/course-outline/configure-modal/UnitTab.jsx +++ b/src/generic/configure-modal/UnitTab.jsx @@ -5,10 +5,12 @@ import { FormattedMessage, injectIntl, useIntl, } from '@edx/frontend-platform/i18n'; import { Field } from 'formik'; +import classNames from 'classnames'; import messages from './messages'; const UnitTab = ({ + isXBlockComponent, values, setFieldValue, showWarning, @@ -18,6 +20,7 @@ const UnitTab = ({ const { isVisibleToStaffOnly, selectedPartitionIndex, + selectedGroups, } = values; const handleChange = (e) => { @@ -26,21 +29,32 @@ const UnitTab = ({ const handleSelect = (e) => { setFieldValue('selectedPartitionIndex', parseInt(e.target.value, 10)); + setFieldValue('selectedGroups', []); + }; + + const checkIsDeletedGroup = (group) => { + const isGroupSelected = selectedGroups.includes(group.id.toString()); + + return group.deleted && isGroupSelected; }; return ( <> -

-
- - - - {showWarning && ( - - - + {!isXBlockComponent && ( + <> +

+
+ + + + {showWarning && ( + + + + )} +
+ )} -
@@ -89,9 +103,19 @@ const UnitTab = ({ value={`${group.id}`} name="selectedGroups" /> - - {group.name} - +
+ + {group.name} + + {group.deleted && ( + + {intl.formatMessage(messages.unitSelectDeletedGroupErrorMessage)} + + )} +
))}
@@ -103,13 +127,21 @@ const UnitTab = ({ ); }; +UnitTab.defaultProps = { + isXBlockComponent: false, +}; + UnitTab.propTypes = { + isXBlockComponent: PropTypes.bool, values: PropTypes.shape({ isVisibleToStaffOnly: PropTypes.bool.isRequired, selectedPartitionIndex: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, ]).isRequired, + selectedGroups: PropTypes.oneOfType([ + PropTypes.string, + ]), }).isRequired, setFieldValue: PropTypes.func.isRequired, showWarning: PropTypes.bool.isRequired, diff --git a/src/course-outline/configure-modal/VisibilityTab.jsx b/src/generic/configure-modal/VisibilityTab.jsx similarity index 98% rename from src/course-outline/configure-modal/VisibilityTab.jsx rename to src/generic/configure-modal/VisibilityTab.jsx index 44ee964619..c6ce99da4d 100644 --- a/src/course-outline/configure-modal/VisibilityTab.jsx +++ b/src/generic/configure-modal/VisibilityTab.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Alert, Form } from '@openedx/paragon'; import { FormattedMessage, injectIntl, useIntl } from '@edx/frontend-platform/i18n'; import messages from './messages'; -import { COURSE_BLOCK_NAMES } from '../constants'; +import { COURSE_BLOCK_NAMES } from '../../constants'; const VisibilityTab = ({ values, diff --git a/src/generic/configure-modal/__mocks__/index.js b/src/generic/configure-modal/__mocks__/index.js new file mode 100644 index 0000000000..8e69d242d9 --- /dev/null +++ b/src/generic/configure-modal/__mocks__/index.js @@ -0,0 +1,199 @@ +export const currentSectionMock = { + displayName: 'Section1', + category: 'chapter', + start: '2025-08-10T10:00:00Z', + visibilityState: true, + format: 'Not Graded', + childInfo: { + displayName: 'Subsection', + children: [ + { + displayName: 'Subsection 1', + id: 1, + category: 'sequential', + due: '', + start: '2025-08-10T10:00:00Z', + visibilityState: true, + defaultTimeLimitMinutes: null, + hideAfterDue: false, + showCorrectness: false, + format: 'Homework', + courseGraders: ['Homework', 'Exam'], + childInfo: { + displayName: 'Unit', + children: [ + { + id: 11, + displayName: 'Subsection_1 Unit 1', + }, + ], + }, + }, + { + displayName: 'Subsection 2', + id: 2, + category: 'sequential', + due: '', + start: '2025-08-10T10:00:00Z', + visibilityState: true, + defaultTimeLimitMinutes: null, + hideAfterDue: false, + showCorrectness: false, + format: 'Homework', + courseGraders: ['Homework', 'Exam'], + childInfo: { + displayName: 'Unit', + children: [ + { + id: 21, + displayName: 'Subsection_2 Unit 1', + }, + ], + }, + }, + { + displayName: 'Subsection 3', + id: 3, + category: 'sequential', + due: '', + start: '2025-08-10T10:00:00Z', + visibilityState: true, + defaultTimeLimitMinutes: null, + hideAfterDue: false, + showCorrectness: false, + format: 'Homework', + courseGraders: ['Homework', 'Exam'], + childInfo: { + children: [], + }, + }, + ], + }, +}; + +export const currentSubsectionMock = { + displayName: 'Subsection 1', + id: 1, + category: 'sequential', + due: '', + start: '2025-08-10T10:00:00Z', + visibilityState: true, + defaultTimeLimitMinutes: null, + hideAfterDue: false, + showCorrectness: false, + format: 'Homework', + courseGraders: ['Homework', 'Exam'], + childInfo: { + displayName: 'Unit', + children: [ + { + id: 11, + displayName: 'Subsection_1 Unit 1', + }, + { + id: 12, + displayName: 'Subsection_1 Unit 2', + }, + ], + }, +}; + +export const currentUnitMock = { + displayName: 'Unit 1', + id: 1, + category: 'vertical', + due: '', + start: '2025-08-10T10:00:00Z', + visibilityState: true, + defaultTimeLimitMinutes: null, + hideAfterDue: false, + showCorrectness: false, + userPartitionInfo: { + selectablePartitions: [ + { + id: 50, + name: 'Enrollment Track Groups', + scheme: 'enrollment_track', + groups: [ + { + id: 6, + name: 'Honor', + selected: false, + deleted: false, + }, + { + id: 2, + name: 'Verified', + selected: false, + deleted: false, + }, + ], + }, + { + id: 1508065533, + name: 'Content Groups', + scheme: 'cohort', + groups: [ + { + id: 1224170703, + name: 'Content Group 1', + selected: false, + deleted: false, + }, + ], + }, + ], + selectedPartitionIndex: -1, + selectedGroupsLabel: '', + }, +}; + +export const currentXBlockMock = { + displayName: 'Unit 1', + id: 1, + category: 'component', + due: '', + start: '2025-08-10T10:00:00Z', + visibilityState: true, + defaultTimeLimitMinutes: null, + hideAfterDue: false, + showCorrectness: false, + userPartitionInfo: { + selectablePartitions: [ + { + id: 50, + name: 'Enrollment Track Groups', + scheme: 'enrollment_track', + groups: [ + { + id: 6, + name: 'Honor', + selected: false, + deleted: false, + }, + { + id: 2, + name: 'Verified', + selected: false, + deleted: false, + }, + ], + }, + { + id: 1508065533, + name: 'Content Groups', + scheme: 'cohort', + groups: [ + { + id: 1224170703, + name: 'Content Group 1', + selected: false, + deleted: false, + }, + ], + }, + ], + selectedPartitionIndex: -1, + selectedGroupsLabel: '', + }, +}; diff --git a/src/course-outline/configure-modal/messages.js b/src/generic/configure-modal/messages.js similarity index 94% rename from src/course-outline/configure-modal/messages.js rename to src/generic/configure-modal/messages.js index 316cbc0fb0..a4b910555f 100644 --- a/src/course-outline/configure-modal/messages.js +++ b/src/generic/configure-modal/messages.js @@ -3,7 +3,11 @@ import { defineMessages } from '@edx/frontend-platform/i18n'; const messages = defineMessages({ title: { id: 'course-authoring.course-outline.configure-modal.title', - defaultMessage: '{title} Settings', + defaultMessage: '{title} settings', + }, + componentTitle: { + id: 'course-authoring.course-outline.configure-modal.component.title', + defaultMessage: 'Editing access for: {title}', }, basicTabTitle: { id: 'course-authoring.course-outline.configure-modal.basic-tab.title', @@ -15,15 +19,15 @@ const messages = defineMessages({ }, releaseDateAndTime: { id: 'course-authoring.course-outline.configure-modal.basic-tab.release-date-and-time', - defaultMessage: 'Release Date and Time', + defaultMessage: 'Release date and time', }, releaseDate: { id: 'course-authoring.course-outline.configure-modal.basic-tab.release-date', - defaultMessage: 'Release Date:', + defaultMessage: 'Release date:', }, releaseTimeUTC: { id: 'course-authoring.course-outline.configure-modal.basic-tab.release-time-UTC', - defaultMessage: 'Release Time in UTC:', + defaultMessage: 'Release time in UTC:', }, visibilityTabTitle: { id: 'course-authoring.course-outline.configure-modal.visibility-tab.title', @@ -31,11 +35,11 @@ const messages = defineMessages({ }, visibilitySectionTitle: { id: 'course-authoring.course-outline.configure-modal.visibility-tab.section-visibility', - defaultMessage: '{visibilityTitle} Visibility', + defaultMessage: '{visibilityTitle} visibility', }, unitVisibility: { id: 'course-authoring.course-outline.configure-modal.visibility-tab.unit-visibility', - defaultMessage: 'Unit Visibility', + defaultMessage: 'Unit visibility', }, hideFromLearners: { id: 'course-authoring.course-outline.configure-modal.visibility.hide-from-learners', @@ -65,6 +69,10 @@ const messages = defineMessages({ id: 'course-authoring.course-outline.configure-modal.unit-tab.unit-select-group-type', defaultMessage: 'Select a group type', }, + unitSelectDeletedGroupErrorMessage: { + id: 'course-authoring.course-outline.configure-modal.unit-tab.unit-select-group-deleted-error-message', + defaultMessage: 'This group no longer exists. Choose another group or remove the access restriction.', + }, unitAllLearnersAndStaff: { id: 'course-authoring.course-outline.configure-modal.unit-tab.unit-all-learners-staff', defaultMessage: 'All Learners and Staff', @@ -87,15 +95,15 @@ const messages = defineMessages({ }, dueDate: { id: 'course-authoring.course-outline.configure-modal.basic-tab.due-date', - defaultMessage: 'Due Date:', + defaultMessage: 'Due date:', }, dueTimeUTC: { id: 'course-authoring.course-outline.configure-modal.basic-tab.due-time-UTC', - defaultMessage: 'Due Time in UTC:', + defaultMessage: 'Due time in UTC:', }, subsectionVisibility: { id: 'course-authoring.course-outline.configure-modal.visibility-tab.subsection-visibility', - defaultMessage: 'Subsection Visibility', + defaultMessage: 'Subsection visibility', }, showEntireSubsection: { id: 'course-authoring.course-outline.configure-modal.visibility-tab.show-entire-subsection', @@ -151,7 +159,7 @@ const messages = defineMessages({ }, setSpecialExam: { id: 'course-authoring.course-outline.configure-modal.advanced-tab.set-special-exam', - defaultMessage: 'Set as a Special Exam', + defaultMessage: 'Set as a special exam', }, none: { id: 'course-authoring.course-outline.configure-modal.advanced-tab.none', @@ -195,7 +203,7 @@ const messages = defineMessages({ }, timeAllotted: { id: 'course-authoring.course-outline.configure-modal.advanced-tab.time-allotted', - defaultMessage: 'Time Allotted (HH:MM):', + defaultMessage: 'Time allotted (HH:MM):', }, timeLimitDescription: { id: 'course-authoring.course-outline.configure-modal.advanced-tab.time-limit-description', diff --git a/src/generic/styles.scss b/src/generic/styles.scss index becfb9a77a..d15f24086a 100644 --- a/src/generic/styles.scss +++ b/src/generic/styles.scss @@ -6,3 +6,4 @@ @import "./create-or-rerun-course/CreateOrRerunCourseForm"; @import "./WysiwygEditor"; @import "./course-stepper/CouseStepper"; +@import "./configure-modal/ConfigureModal"; diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 5ddf05c815..42eccd7745 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -1076,5 +1076,9 @@ "course-authoring.course-unit.modal.make-visibility.btn.action.text": "Make visible to students", "course-authoring.course-unit.modal.make-visibility.btn.cancel.text": "Cancel", "course-authoring.course-unit.modal.make-visibility.description": "If the unit was previously published and released to students, any changes you made to the unit when it was hidden will now be visible to students. Do you want to proceed?", - "course-authoring.course-unit.xblock.button.actions.alt": "Actions" + "course-authoring.course-unit.xblock.button.actions.alt": "Actions", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index ad312d6647..3790139ac2 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/de_DE.json b/src/i18n/messages/de_DE.json index ad043c3f7c..b637890bca 100644 --- a/src/i18n/messages/de_DE.json +++ b/src/i18n/messages/de_DE.json @@ -1077,5 +1077,76 @@ "course-authoring.course-unit.modal.make-visibility.title": "Make visible to students", "course-authoring.course-unit.modal.make-visibility.btn.action.text": "Make visible to students", "course-authoring.course-unit.modal.make-visibility.btn.cancel.text": "Cancel", - "course-authoring.course-unit.modal.make-visibility.description": "If the unit was previously published and released to students, any changes you made to the unit when it was hidden will now be visible to students. Do you want to proceed?" + "course-authoring.course-unit.modal.make-visibility.description": "If the unit was previously published and released to students, any changes you made to the unit when it was hidden will now be visible to students. Do you want to proceed?", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/es_419.json b/src/i18n/messages/es_419.json index 8ecd721182..34852e8a0b 100644 --- a/src/i18n/messages/es_419.json +++ b/src/i18n/messages/es_419.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/fa_IR.json b/src/i18n/messages/fa_IR.json index a9d33d6636..c842bc4ebd 100644 --- a/src/i18n/messages/fa_IR.json +++ b/src/i18n/messages/fa_IR.json @@ -100,5 +100,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 6287473708..3ca31c0c04 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/fr_CA.json b/src/i18n/messages/fr_CA.json index 071a2b2934..597e10678c 100644 --- a/src/i18n/messages/fr_CA.json +++ b/src/i18n/messages/fr_CA.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index ad312d6647..3790139ac2 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index ad312d6647..3790139ac2 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/it_IT.json b/src/i18n/messages/it_IT.json index b743fe0365..ab1c2b9bf5 100644 --- a/src/i18n/messages/it_IT.json +++ b/src/i18n/messages/it_IT.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index ad312d6647..a546eda767 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/pt_PT.json b/src/i18n/messages/pt_PT.json index b0567f5942..5acceaa968 100644 --- a/src/i18n/messages/pt_PT.json +++ b/src/i18n/messages/pt_PT.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index ad312d6647..3790139ac2 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/uk.json b/src/i18n/messages/uk.json index ad312d6647..3790139ac2 100644 --- a/src/i18n/messages/uk.json +++ b/src/i18n/messages/uk.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." } diff --git a/src/i18n/messages/zh_CN.json b/src/i18n/messages/zh_CN.json index ad312d6647..3790139ac2 100644 --- a/src/i18n/messages/zh_CN.json +++ b/src/i18n/messages/zh_CN.json @@ -1077,5 +1077,91 @@ "course-authoring.group-configurations.empty-placeholder.button": "Add your first content group", "course-authoring.group-configurations.experimental-empty-placeholder.button": "Add your first group configuration", "course-authoring.group-configurations.content-groups.add-new-group": "New content group", - "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience." + "course-authoring.group-configurations.sidebar.about.title": "Content groups", + "course-authoring.group-configurations.sidebar.about.description-1": "If you have cohorts enabled in your course, you can use content groups to create cohort-specific courseware. In other words, you can customize the content that particular cohorts see in your course.", + "course-authoring.group-configurations.sidebar.about.description-2": "Each content group that you create can be associated with one or more cohorts. In addition to making course content available to all learners, you can restrict access to some content to learners in specific content groups. Only learners in the cohorts that are associated with the specified content groups see the additional content.", + "course-authoring.group-configurations.sidebar.about.description-3": "Click {strongText} to add a new content group. To edit the name of a content group, hover over its box and click {strongText2}. You can delete a content group only if it is not in use by a unit. To delete a content group, hover over its box and click the delete icon.Drag sections, subsections, and units to new locations in the outline.", + "course-authoring.group-configurations.sidebar.about.description-3.strong": "New content group", + "course-authoring.group-configurations.sidebar.about-2.title": "Experiment group configurations", + "course-authoring.group-configurations.sidebar.about-2.description-1": "Use experiment group configurations if you are conducting content experiments, also known as A/B testing, in your course. Experiment group configurations define how many groups of learners are in a content experiment. When you create a content experiment for a course, you select the group configuration to use.", + "course-authoring.group-configurations.sidebar.about-2.description-2": "Click {strongText} to add a new configuration. To edit a configuration, hover over its box and click {strongText2}. You can delete a group configuration only if it is not in use in an experiment. To delete a configuration, hover over its box and click the delete.", + "course-authoring.group-configurations.sidebar.about-2.description-2.strong": "New group configuration", + "course-authoring.group-configurations.sidebar.about-3.title": "Enrollment track groups", + "course-authoring.group-configurations.sidebar.about-3.description-1": "Enrollment track groups allow you to offer different course content to learners in each enrollment track. Learners enrolled in each enrollment track in your course are automatically included in the corresponding enrollment track group.", + "course-authoring.group-configurations.sidebar.about-3.description-2": "On unit pages in the course outline, you can restrict access to components to learners based on their enrollment track.", + "course-authoring.group-configurations.sidebar.about-3.description-3": "You cannot edit enrollment track groups, but you can expand each group to view details of the course content that is designated for learners in the group.", + "course-authoring.group-configurations.sidebar.about.description.strong-edit": "edit", + "course-authoring.group-configurations.sidebar.learnmore.button": "Learn more", + "course-authoring.course-unit.general.alert.unpublished-version.description": "Note: The last published version of this unit is live. By publishing changes you will change the student experience.", + "course-authoring.textbooks.header.title": "Textbooks", + "course-authoring.textbooks.header.breadcrumb.content": "Content", + "course-authoring.textbooks.header.breadcrumb.pages-and-resources": "Pages & resources", + "course-authoring.textbooks.header.breadcrumb.aria-label": "Textbook breadcrumb", + "course-authoring.textbooks.header.new-textbook": "New textbook", + "course-authoring.textbooks.empty-placeholder.title": "You haven't added any textbooks to this course yet.", + "course-authoring.textbooks.empty-placeholder.button.new-textbook": "Add your first textbook", + "course-authoring.textbooks.chapters.title": "{count} PDF chapters", + "course-authoring.textbooks.button.view": "View the PDF live", + "course-authoring.textbooks.button.view.alt": "textbook-view-button", + "course-authoring.textbooks.button.edit": "Edit", + "course-authoring.textbooks.button.edit.alt": "textbook-edit-button", + "course-authoring.textbooks.button.delete": "Delete", + "course-authoring.textbooks.button.delete.alt": "textbook-delete-button", + "course-authoring.textbooks.sidebar.section-1.title": "Why should I break my textbook into chapters?", + "course-authoring.textbooks.sidebar.section-1.descriptions": "Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.", + "course-authoring.textbooks.sidebar.section-2.title": "What if my book isn't divided into chapters?", + "course-authoring.textbooks.sidebar.section-2.descriptions": "If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.", + "course-authoring.textbooks.sidebar.section-link": "Learn more", + "course-authoring.textbooks.form.tab-title.label": "Textbook name", + "course-authoring.textbooks.form.tab-title.placeholder": "Introduction to Cookie Baking", + "course-authoring.textbooks.form.tab-title.helper-text": "provide the title/name of the text book as you would like your students to see it", + "course-authoring.textbooks.form.tab-title.validation-text": "Textbook name is required", + "course-authoring.textbooks.form.chapter.title.label": "Chapter name", + "course-authoring.textbooks.form.chapter.title.placeholder": "Chapter {value}", + "course-authoring.textbooks.form.chapter.title.helper-text": "provide the title/name of the chapter that will be used in navigating", + "course-authoring.textbooks.form.chapter.title.validation-text": "Chapter name is required", + "course-authoring.textbooks.form.chapter.url.label": "Chapter asset", + "course-authoring.textbooks.form.chapter.url.placeholder": "path/to/introductionToCookieBaking-CH1.pdf", + "course-authoring.textbooks.form.chapter.url.helper-text": "upload a PDF file or provide the path to a Studio asset file", + "course-authoring.textbooks.form.chapter.url.validation-text": "Chapter asset is required", + "course-authoring.textbooks.form.add-chapter.helper-text": "Please add at least one chapter", + "course-authoring.textbooks.form.add-chapter.button": "Add a chapter", + "course-authoring.textbooks.form.upload-button.tooltip": "Upload", + "course-authoring.textbooks.form.upload-button.alt": "chapter-upload-button", + "course-authoring.textbooks.form.delete-button.tooltip": "Delete", + "course-authoring.textbooks.form.delete-button.alt": "chapter-delete-button", + "course-authoring.textbooks.form.button.cancel": "Cancel", + "course-authoring.textbooks.form.button.save": "Save", + "course-authoring.textbooks.form.upload-modal.title": "Upload a new PDF to “{courseName}”", + "course-authoring.textbooks.form.upload-modal.dropzone-text": "Drag and drop your PDF file here or click to upload", + "course-authoring.textbooks.form.upload-modal.help-text": "File must be in PDF format", + "course-authoring.textbooks.form.delete-modal.title": "Delete “{textbookTitle}”?", + "course-authoring.textbooks.form.delete-modal.description": "Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed.", + "course-authoring.course-unit.xblock.iframe.error.text": "Unit iframe failed to load. Server possibly returned 4xx or 5xx response.", + "course-authoring.course-unit.paste-component.btn.text": "Paste component", + "course-authoring.course-unit.popover.content.text": "From:", + "course-authoring.course-unit.paste-component.whats-in-clipboard.text": "What's in my clipboard?", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.title": "Files need to be updated manually.", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.description": "The following files must be updated manually for components to work as intended:", + "course-authoring.course-unit.paste-notification.has-conflicting-errors.button.text": "Upload files", + "course-authoring.course-unit.paste-notification.has-errors.title": "Some errors occurred", + "course-authoring.course-unit.paste-notification.has-errors.description": "The following required files could not be added to the course:", + "course-authoring.course-unit.paste-notification.has-new-files.title": "New file(s) added to Files & Uploads.", + "course-authoring.course-unit.paste-notification.has-new-files.description": "The following required files were imported to this course:", + "course-authoring.course-unit.paste-notification.has-new-files.button.text": "View files", + "course-authoring.group-configurations.container.delete-modal.subtitle": "content group", + "course-authoring.group-configurations.container.delete-restriction": "Cannot delete when in use by a unit", + "course-authoring.group-configurations.content-groups.new-group.header": "Content group name *", + "course-authoring.group-configurations.content-groups.new-group.input.placeholder": "This is the name of the group", + "course-authoring.group-configurations.content-groups.new-group.invalid-message": "All groups must have a unique name.", + "course-authoring.group-configurations.content-groups.new-group.cancel": "Cancel", + "course-authoring.group-configurations.content-groups.edit-group.delete": "Delete", + "course-authoring.group-configurations.content-groups.new-group.create": "Create", + "course-authoring.group-configurations.content-groups.edit-group.save": "Save", + "course-authoring.group-configurations.content-groups.new-group.required-error": "Group name is required", + "course-authoring.group-configurations.content-groups.edit-group.alert-group-in-usage": "This content group is used in one or more units.", + "course-authoring.course-unit.xblock.visibility.message": "Access restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.xblock.validation.summary": "This component has validation issues.", + "course-authoring.course-unit.heading.visibility.defined.message": "Access to this unit is restricted to: {selectedGroupsLabel}", + "course-authoring.course-unit.heading.visibility.common.message": "Access to some content in this unit is restricted to specific groups of learners." }