diff --git a/client/app/api/course/Gradebook.ts b/client/app/api/course/Gradebook.ts index e00c94a64c..7603f1f2a1 100644 --- a/client/app/api/course/Gradebook.ts +++ b/client/app/api/course/Gradebook.ts @@ -1,4 +1,4 @@ -import { GradebookData } from 'types/course/gradebook'; +import { GradebookData, UpdateWeightsPayload } from 'types/course/gradebook'; import { APIResponse } from 'api/types'; @@ -12,4 +12,10 @@ export default class GradebookAPI extends BaseCourseAPI { index(): APIResponse { return this.client.get(this.#urlPrefix); } + + updateWeights( + payload: UpdateWeightsPayload, + ): APIResponse { + return this.client.patch(`${this.#urlPrefix}/weights`, payload); + } } diff --git a/client/app/bundles/course/gradebook/__tests__/ConfigureWeightsDialog.test.tsx b/client/app/bundles/course/gradebook/__tests__/ConfigureWeightsDialog.test.tsx new file mode 100644 index 0000000000..4ec677ae6f --- /dev/null +++ b/client/app/bundles/course/gradebook/__tests__/ConfigureWeightsDialog.test.tsx @@ -0,0 +1,97 @@ +import { fireEvent, render, screen, waitFor } from 'test-utils'; + +import * as operations from '../operations'; +import ConfigureWeightsDialog from '../components/ConfigureWeightsDialog'; + +jest + .spyOn(operations, 'updateGradebookWeights') + .mockReturnValue(() => Promise.resolve()); + +const categories = [{ id: 1, title: 'Missions' }]; +const tabs = [ + { id: 10, title: 'Assignments', categoryId: 1, gradebookWeight: 50 }, + { id: 11, title: 'Optional', categoryId: 1, gradebookWeight: 50 }, +]; + +const setup = (overrides = {}) => + render( + , + ); + +describe('', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders one input per tab grouped by category', async () => { + setup(); + expect(await screen.findByText('Missions')).toBeInTheDocument(); + expect(screen.getByLabelText('Assignments')).toHaveValue(50); + expect(screen.getByLabelText('Optional')).toHaveValue(50); + }); + + it('shows Total: 100% with no warning when sum = 100', async () => { + setup(); + expect(await screen.findByText(/Total:\s*100%/)).toBeInTheDocument(); + expect(screen.queryByText(/do not sum to 100/i)).not.toBeInTheDocument(); + }); + + it('shows warning when sum != 100', async () => { + setup(); + await screen.findByText('Missions'); + fireEvent.change(screen.getByLabelText('Optional'), { + target: { value: '30' }, + }); + expect(screen.getByText(/Total:\s*80%/)).toBeInTheDocument(); + expect(screen.getByText(/do not sum to 100/i)).toBeInTheDocument(); + }); + + it('shows inline error for >100', async () => { + setup(); + await screen.findByText('Missions'); + fireEvent.change(screen.getByLabelText('Assignments'), { + target: { value: '101' }, + }); + expect(screen.getByText(/must be at most 100/i)).toBeInTheDocument(); + }); + + it('shows inline error for negative', async () => { + setup(); + await screen.findByText('Missions'); + fireEvent.change(screen.getByLabelText('Optional'), { + target: { value: '-1' }, + }); + expect(screen.getByText(/must be at least 0/i)).toBeInTheDocument(); + }); + + it('Save dispatches updateGradebookWeights with current values', async () => { + setup(); + await screen.findByText('Missions'); + fireEvent.change(screen.getByLabelText('Optional'), { + target: { value: '40' }, + }); + fireEvent.click(screen.getByRole('button', { name: /save/i })); + await waitFor(() => { + expect(operations.updateGradebookWeights).toHaveBeenCalledWith([ + { tabId: 10, weight: 50 }, + { tabId: 11, weight: 40 }, + ]); + }); + }); + + it('Cancel does not dispatch', async () => { + setup(); + await screen.findByText('Missions'); + fireEvent.change(screen.getByLabelText('Optional'), { + target: { value: '40' }, + }); + fireEvent.click(screen.getByRole('button', { name: /cancel/i })); + expect(operations.updateGradebookWeights).not.toHaveBeenCalled(); + }); +}); diff --git a/client/app/bundles/course/gradebook/__tests__/GradebookIndex.test.tsx b/client/app/bundles/course/gradebook/__tests__/GradebookIndex.test.tsx index e0fc76ae2c..74a2a30262 100644 --- a/client/app/bundles/course/gradebook/__tests__/GradebookIndex.test.tsx +++ b/client/app/bundles/course/gradebook/__tests__/GradebookIndex.test.tsx @@ -32,6 +32,8 @@ const emptyState = { students: [], submissions: [], gamificationEnabled: false, + weightedViewEnabled: false, + canManageWeights: false, }, }; @@ -43,6 +45,8 @@ const noStudentsState = { students: [], submissions: [], gamificationEnabled: false, + weightedViewEnabled: false, + canManageWeights: false, }, }; @@ -62,6 +66,8 @@ const populatedState = { ], submissions: [{ studentId: 1, assessmentId: 100, grade: 8 }], gamificationEnabled: false, + weightedViewEnabled: false, + canManageWeights: false, }, }; @@ -143,4 +149,44 @@ describe('GradebookIndex', () => { ), ).toBeInTheDocument(); }); + + it('does not render view toggle when weightedViewEnabled is false', async () => { + render(, { state: populatedState }); + await screen.findByText('Gradebook'); + expect( + screen.queryByRole('button', { name: /by weight/i }), + ).not.toBeInTheDocument(); + }); + + it('renders view toggle when weightedViewEnabled is true', async () => { + render(, { + state: { + gradebook: { + ...populatedState.gradebook, + weightedViewEnabled: true, + canManageWeights: false, + }, + }, + }); + expect( + await screen.findByRole('button', { name: /all assessments/i }), + ).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: /by weight/i }), + ).toBeInTheDocument(); + }); + + it('switches to By weight view on toggle click', async () => { + render(, { + state: { + gradebook: { + ...populatedState.gradebook, + weightedViewEnabled: true, + canManageWeights: false, + }, + }, + }); + fireEvent.click(await screen.findByRole('button', { name: /by weight/i })); + expect(screen.getByTestId('gradebook-weighted-table')).toBeInTheDocument(); + }); }); diff --git a/client/app/bundles/course/gradebook/__tests__/GradebookWeightedTable.test.tsx b/client/app/bundles/course/gradebook/__tests__/GradebookWeightedTable.test.tsx new file mode 100644 index 0000000000..c1d34bccb8 --- /dev/null +++ b/client/app/bundles/course/gradebook/__tests__/GradebookWeightedTable.test.tsx @@ -0,0 +1,146 @@ +import userEvent from '@testing-library/user-event'; +import { render, screen, within } from 'test-utils'; + +import type { + AssessmentData, + CategoryData, + StudentData, + SubmissionData, + TabData, +} from '../types'; +import GradebookWeightedTable from '../components/GradebookWeightedTable'; + +// Suppress MUI dialog rendering noise in jsdom +jest.mock('../components/ConfigureWeightsDialog', () => ({ + __esModule: true, + default: () => null, +})); + +const categories: CategoryData[] = [{ id: 1, title: 'Missions' }]; +const tabs: TabData[] = [ + { id: 10, title: 'Assignments', categoryId: 1, gradebookWeight: 60 }, + { id: 11, title: 'Optional', categoryId: 1, gradebookWeight: 40 }, +]; +const assessments: AssessmentData[] = [ + { id: 100, title: 'Quiz 1', tabId: 10, maxGrade: 100 }, + { id: 101, title: 'Quiz 2', tabId: 11, maxGrade: 50 }, +]; +const students: StudentData[] = [ + { id: 1, name: 'Alice', email: 'alice@example.com', level: 3, totalXp: 150 }, +]; +const submissions: SubmissionData[] = [ + { studentId: 1, assessmentId: 100, grade: 80 }, + { studentId: 1, assessmentId: 101, grade: 40 }, +]; + +const defaultProps = { + categories, + tabs, + assessments, + students, + submissions, + canManageWeights: true, +}; + +const renderTable = (props = {}) => + render(); + +describe('', () => { + it('renders category header in row 1', async () => { + renderTable(); + expect(await screen.findByText('Missions')).toBeInTheDocument(); + }); + + it('renders tab titles in row 2', async () => { + renderTable(); + await screen.findByText('Missions'); + expect(screen.getByText('Assignments')).toBeInTheDocument(); + expect(screen.getByText('Optional')).toBeInTheDocument(); + }); + + it('renders "X% of grade" subheaders in row 3', async () => { + renderTable(); + await screen.findByText('Missions'); + expect(screen.getByText('60% of grade')).toBeInTheDocument(); + expect(screen.getByText('40% of grade')).toBeInTheDocument(); + }); + + it('shows "100% total" in Total subheader when weights sum to 100', async () => { + renderTable(); + await screen.findByText('Missions'); + expect(screen.getByText('100% total')).toBeInTheDocument(); + }); + + it('shows warning text in Total subheader when weights do not sum to 100', async () => { + const tabsUnbalanced: TabData[] = [ + { id: 10, title: 'Assignments', categoryId: 1, gradebookWeight: 60 }, + { id: 11, title: 'Optional', categoryId: 1, gradebookWeight: 30 }, + ]; + renderTable({ tabs: tabsUnbalanced }); + await screen.findByText('Missions'); + // subheader should show 90% total with a warning indicator + expect(screen.getByText(/90%\s*total/)).toBeInTheDocument(); + }); + + it('computes and displays tab subtotals for a student', async () => { + renderTable(); + await screen.findByText('Alice'); + // tab 10: 80/100 = 80.00%, tab 11: 40/50 = 80.00% — both show the same value + const cells = screen.getAllByText('80.00%'); + expect(cells.length).toBeGreaterThanOrEqual(2); + }); + + it('computes and displays weighted total for a student', async () => { + renderTable(); + await screen.findByText('Alice'); + // total = (60 * 0.8 + 40 * 0.8) / 100 = 0.8 = 80.00% + const allCells = screen.getAllByText('80.00%'); + expect(allCells.length).toBeGreaterThanOrEqual(2); + }); + + it('shows — for student with no graded submissions in a tab', async () => { + renderTable({ submissions: [] }); + await screen.findByText('Alice'); + // No submissions → all dashes + expect(screen.getAllByText('—').length).toBeGreaterThanOrEqual(1); + }); + + it('recomputes when Treat Ungraded as 0 is toggled', async () => { + const user = userEvent.setup(); + renderTable({ + submissions: [{ studentId: 1, assessmentId: 100, grade: 80 }], + }); + await screen.findByText('Alice'); + // Before toggle: tab 11 ungraded → dash + expect(screen.getAllByText('—').length).toBeGreaterThanOrEqual(1); + // Toggle on + const toggle = screen.getByRole('checkbox', { name: /treat ungraded as 0/i }); + await user.click(toggle); + // After toggle: tab 11 = 0/50 = 0.00% + expect(screen.getByText('0.00%')).toBeInTheDocument(); + }); + + it('shows empty state banner when all weights are 0', async () => { + const zeroTabs: TabData[] = [ + { id: 10, title: 'Assignments', categoryId: 1, gradebookWeight: 0 }, + ]; + renderTable({ tabs: zeroTabs }); + await screen.findByText(/no tab weights configured/i); + }); + + it('shows Configure Weights button when canManageWeights = true', async () => { + renderTable({ canManageWeights: true }); + await screen.findByText('Missions'); + expect( + screen.getByRole('button', { name: /configure weights/i }), + ).toBeInTheDocument(); + }); + + it('hides Configure Weights button when canManageWeights = false', async () => { + renderTable({ canManageWeights: false }); + await screen.findByText('Missions'); + expect( + screen.queryByRole('button', { name: /configure weights/i }), + ).not.toBeInTheDocument(); + }); +}); diff --git a/client/app/bundles/course/gradebook/__tests__/computeWeighted.test.ts b/client/app/bundles/course/gradebook/__tests__/computeWeighted.test.ts new file mode 100644 index 0000000000..093ca6599a --- /dev/null +++ b/client/app/bundles/course/gradebook/__tests__/computeWeighted.test.ts @@ -0,0 +1,154 @@ +import { computeTabSubtotal, computeStudentTotal, sumWeights } from '../computeWeighted'; + +const assessments = [ + { id: 1, tabId: 10, maxGrade: 100, title: 'A' }, + { id: 2, tabId: 10, maxGrade: 50, title: 'B' }, + { id: 3, tabId: 20, maxGrade: 100, title: 'C' }, +]; + +const subs = ( + entries: { studentId: number; assessmentId: number; grade: number | null }[], +) => entries; + +describe('computeTabSubtotal', () => { + it('returns null when tab has no assessments', () => { + expect( + computeTabSubtotal({ + studentId: 1, + tab: { id: 999, title: 'X', categoryId: 0 }, + assessments, + submissions: [], + treatUngradedAsZero: false, + }), + ).toBeNull(); + }); + + it('returns null when student has no graded submissions and toggle off', () => { + expect( + computeTabSubtotal({ + studentId: 1, + tab: { id: 10, title: 'M', categoryId: 0 }, + assessments, + submissions: [], + treatUngradedAsZero: false, + }), + ).toBeNull(); + }); + + it('sum-of-points across graded only when toggle off', () => { + expect( + computeTabSubtotal({ + studentId: 1, + tab: { id: 10, title: 'M', categoryId: 0 }, + assessments, + submissions: subs([ + { studentId: 1, assessmentId: 1, grade: 80 }, + // assessment 2 ungraded + ]), + treatUngradedAsZero: false, + }), + ).toBeCloseTo(0.8); + }); + + it('includes ungraded as zero when toggle on', () => { + expect( + computeTabSubtotal({ + studentId: 1, + tab: { id: 10, title: 'M', categoryId: 0 }, + assessments, + submissions: subs([{ studentId: 1, assessmentId: 1, grade: 80 }]), + treatUngradedAsZero: true, + }), + ).toBeCloseTo(80 / 150); + }); +}); + +describe('computeStudentTotal', () => { + const tabs = [ + { id: 10, title: 'M', categoryId: 0, gradebookWeight: 60 }, + { id: 20, title: 'T', categoryId: 0, gradebookWeight: 40 }, + ]; + + it('weighted average over weighted tabs', () => { + const total = computeStudentTotal({ + studentId: 1, + tabs, + assessments, + submissions: subs([ + { studentId: 1, assessmentId: 1, grade: 80 }, + { studentId: 1, assessmentId: 2, grade: 50 }, + { studentId: 1, assessmentId: 3, grade: 90 }, + ]), + treatUngradedAsZero: false, + }); + // tab 10 subtotal = 130/150; tab 20 subtotal = 90/100 + // total = (60*(130/150) + 40*0.9) / 100 + expect(total).toBeCloseTo((60 * (130 / 150) + 40 * 0.9) / 100); + }); + + it('excludes tabs with weight 0', () => { + const total = computeStudentTotal({ + studentId: 1, + tabs: [ + { id: 10, title: 'M', categoryId: 0, gradebookWeight: 100 }, + { id: 20, title: 'T', categoryId: 0, gradebookWeight: 0 }, + ], + assessments, + submissions: subs([ + { studentId: 1, assessmentId: 1, grade: 80 }, + { studentId: 1, assessmentId: 2, grade: 50 }, + ]), + treatUngradedAsZero: false, + }); + expect(total).toBeCloseTo(130 / 150); + }); + + it('returns null when no weighted tab contributes', () => { + expect( + computeStudentTotal({ + studentId: 1, + tabs: [{ id: 10, title: 'M', categoryId: 0, gradebookWeight: 0 }], + assessments, + submissions: [], + treatUngradedAsZero: false, + }), + ).toBeNull(); + }); + + it('normalizes when weights do not sum to 100', () => { + const total = computeStudentTotal({ + studentId: 1, + tabs: [ + { id: 10, title: 'M', categoryId: 0, gradebookWeight: 60 }, + { id: 20, title: 'T', categoryId: 0, gradebookWeight: 30 }, + ], + assessments, + submissions: subs([ + { studentId: 1, assessmentId: 1, grade: 80 }, + { studentId: 1, assessmentId: 2, grade: 50 }, + { studentId: 1, assessmentId: 3, grade: 90 }, + ]), + treatUngradedAsZero: false, + }); + // weightSum = 90; total = (60*(130/150) + 30*0.9)/90 + expect(total).toBeCloseTo((60 * (130 / 150) + 30 * 0.9) / 90); + }); +}); + +describe('sumWeights', () => { + it('sums gradebookWeight across tabs', () => { + const tabs = [ + { id: 10, title: 'M', categoryId: 0, gradebookWeight: 60 }, + { id: 20, title: 'T', categoryId: 0, gradebookWeight: 40 }, + ]; + expect(sumWeights(tabs)).toBe(100); + }); + + it('treats undefined gradebookWeight as 0', () => { + const tabs = [ + { id: 10, title: 'M', categoryId: 0 }, + { id: 20, title: 'T', categoryId: 0, gradebookWeight: 50 }, + ]; + expect(sumWeights(tabs)).toBe(50); + }); +}); diff --git a/client/app/bundles/course/gradebook/components/ConfigureWeightsDialog.tsx b/client/app/bundles/course/gradebook/components/ConfigureWeightsDialog.tsx new file mode 100644 index 0000000000..1d78030aca --- /dev/null +++ b/client/app/bundles/course/gradebook/components/ConfigureWeightsDialog.tsx @@ -0,0 +1,193 @@ +import { FC, useEffect, useState } from 'react'; +import { defineMessages } from 'react-intl'; +import { + Alert, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Stack, + TextField, + Typography, +} from '@mui/material'; +import type { CategoryData, TabData } from 'types/course/gradebook'; + +import { useAppDispatch } from 'lib/hooks/store'; +import toast from 'lib/hooks/toast'; +import useTranslation from 'lib/hooks/useTranslation'; + +import { updateGradebookWeights } from '../operations'; + +interface Props { + open: boolean; + onClose: () => void; + categories: CategoryData[]; + tabs: TabData[]; +} + +const translations = defineMessages({ + title: { + id: 'course.gradebook.ConfigureWeightsDialog.title', + defaultMessage: 'Configure tab weights', + }, + description: { + id: 'course.gradebook.ConfigureWeightsDialog.description', + defaultMessage: + 'Set how much each tab contributes to the total grade. Weights should sum to 100.', + }, + totalLabel: { + id: 'course.gradebook.ConfigureWeightsDialog.totalLabel', + defaultMessage: 'Total: {sum}%', + }, + sumWarning: { + id: 'course.gradebook.ConfigureWeightsDialog.sumWarning', + defaultMessage: + 'Weights do not sum to 100. Saving is allowed; Total may be inaccurate.', + }, + cancel: { + id: 'course.gradebook.ConfigureWeightsDialog.cancel', + defaultMessage: 'Cancel', + }, + save: { + id: 'course.gradebook.ConfigureWeightsDialog.save', + defaultMessage: 'Save', + }, + saveSuccess: { + id: 'course.gradebook.ConfigureWeightsDialog.saveSuccess', + defaultMessage: 'Weights saved.', + }, + saveFailure: { + id: 'course.gradebook.ConfigureWeightsDialog.saveFailure', + defaultMessage: 'Failed to save weights — try again.', + }, + errorMin: { + id: 'course.gradebook.ConfigureWeightsDialog.errorMin', + defaultMessage: 'Value must be at least 0', + }, + errorMax: { + id: 'course.gradebook.ConfigureWeightsDialog.errorMax', + defaultMessage: 'Value must be at most 100', + }, + errorInteger: { + id: 'course.gradebook.ConfigureWeightsDialog.errorInteger', + defaultMessage: 'Value must be a whole number', + }, +}); + +const validate = (value: number): keyof typeof translations | null => { + if (!Number.isInteger(value)) return 'errorInteger'; + if (value < 0) return 'errorMin'; + if (value > 100) return 'errorMax'; + return null; +}; + +const ConfigureWeightsDialog: FC = ({ + open, + onClose, + categories, + tabs, +}) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const [weights, setWeights] = useState>(() => + Object.fromEntries(tabs.map((tb) => [tb.id, tb.gradebookWeight ?? 0])), + ); + const [submitting, setSubmitting] = useState(false); + + // Re-sync from store whenever dialog opens (handles stale state after external updates) + useEffect(() => { + if (open) { + setWeights( + Object.fromEntries(tabs.map((tb) => [tb.id, tb.gradebookWeight ?? 0])), + ); + } + }, [open, tabs]); + + const sum = Object.values(weights).reduce((acc, w) => acc + w, 0); + const hasInvalid = Object.values(weights).some((w) => validate(w) !== null); + + const handleChange = (tabId: number, raw: string): void => { + const parsed = raw === '' ? 0 : Number(raw); + setWeights((prev) => ({ ...prev, [tabId]: parsed })); + }; + + const handleSave = async (): Promise => { + if (hasInvalid) return; + setSubmitting(true); + try { + await dispatch( + updateGradebookWeights( + tabs.map((tb) => ({ tabId: tb.id, weight: weights[tb.id] ?? 0 })), + ), + ); + toast.success(t(translations.saveSuccess)); + onClose(); + } catch { + toast.error(t(translations.saveFailure)); + } finally { + setSubmitting(false); + } + }; + + return ( + + {t(translations.title)} + + + {t(translations.description)} + + + {categories.map((cat) => ( +
+ {cat.title} + + {tabs + .filter((tb) => tb.categoryId === cat.id) + .map((tb) => { + const value = weights[tb.id] ?? 0; + const errKey = validate(value); + return ( + handleChange(tb.id, e.target.value)} + size="small" + type="number" + value={value} + /> + ); + })} + +
+ ))} +
+ + {t(translations.totalLabel, { sum })} + + {sum !== 100 && ( + + {t(translations.sumWarning)} + + )} +
+ + + + +
+ ); +}; + +export default ConfigureWeightsDialog; diff --git a/client/app/bundles/course/gradebook/components/GradebookWeightedTable.tsx b/client/app/bundles/course/gradebook/components/GradebookWeightedTable.tsx new file mode 100644 index 0000000000..50d0dce48e --- /dev/null +++ b/client/app/bundles/course/gradebook/components/GradebookWeightedTable.tsx @@ -0,0 +1,300 @@ +import { FC, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { defineMessages } from 'react-intl'; +import WarningAmberIcon from '@mui/icons-material/WarningAmber'; +import { + Button, + FormControlLabel, + Paper, + Switch, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Tooltip, + Typography, +} from '@mui/material'; +import type { + AssessmentData, + CategoryData, + StudentData, + SubmissionData, + TabData, +} from 'types/course/gradebook'; + +import useTranslation from 'lib/hooks/useTranslation'; + +import { + computeStudentTotal, + computeTabSubtotal, + sumWeights, +} from '../computeWeighted'; + +import ConfigureWeightsDialog from './ConfigureWeightsDialog'; + +interface Props { + categories: CategoryData[]; + tabs: TabData[]; + assessments: AssessmentData[]; + students: StudentData[]; + submissions: SubmissionData[]; + canManageWeights: boolean; +} + +const translations = defineMessages({ + configureWeights: { + id: 'course.gradebook.GradebookWeightedTable.configure', + defaultMessage: 'Configure Weights', + }, + treatUngradedAsZero: { + id: 'course.gradebook.GradebookWeightedTable.treatUngradedAsZero', + defaultMessage: 'Treat Ungraded as 0', + }, + total: { + id: 'course.gradebook.GradebookWeightedTable.total', + defaultMessage: 'Total', + }, + totalSubheader: { + id: 'course.gradebook.GradebookWeightedTable.totalSubheader', + defaultMessage: '{sum}% total', + }, + sumWarningTooltip: { + id: 'course.gradebook.GradebookWeightedTable.sumWarningTooltip', + defaultMessage: 'Tab weights sum to {sum}%. Configure Weights to fix.', + }, + weightSubheader: { + id: 'course.gradebook.GradebookWeightedTable.weightSubheader', + defaultMessage: '{weight}% of grade', + }, + emptyStateTitle: { + id: 'course.gradebook.GradebookWeightedTable.emptyStateTitle', + defaultMessage: 'No tab weights configured.', + }, + emptyStateBody: { + id: 'course.gradebook.GradebookWeightedTable.emptyStateBody', + defaultMessage: 'Click Configure Weights to start.', + }, + name: { + id: 'course.gradebook.GradebookWeightedTable.name', + defaultMessage: 'Name', + }, +}); + +const formatPct = (v: number | null): string => + v == null ? '—' : `${(v * 100).toFixed(2)}%`; + +const GradebookWeightedTable: FC = ({ + categories, + tabs, + assessments, + students, + submissions, + canManageWeights, +}) => { + const { t } = useTranslation(); + const [treatUngradedAsZero, setTreatUngradedAsZero] = useState(false); + const [dialogOpen, setDialogOpen] = useState(false); + + // 3-row sticky header measurement + const row1Ref = useRef(null); + const row2Ref = useRef(null); + const [row2Top, setRow2Top] = useState(0); + const [row3Top, setRow3Top] = useState(0); + useLayoutEffect(() => { + const h1 = row1Ref.current?.offsetHeight ?? 0; + const h2 = row2Ref.current?.offsetHeight ?? 0; + setRow2Top(h1); + setRow3Top(h1 + h2); + }, [tabs, categories]); + + const rows = useMemo( + () => + students.map((stu) => { + const subtotalsByTabId: Record = + Object.fromEntries( + tabs.map((tab) => [ + tab.id, + computeTabSubtotal({ + studentId: stu.id, + tab, + assessments, + submissions, + treatUngradedAsZero, + }), + ]), + ); + const total = computeStudentTotal({ + studentId: stu.id, + tabs, + assessments, + submissions, + treatUngradedAsZero, + }); + return { + studentId: stu.id, + name: stu.name, + subtotalsByTabId, + total, + }; + }), + [students, tabs, assessments, submissions, treatUngradedAsZero], + ); + + const weightSum = sumWeights(tabs); + const allWeightsZero = weightSum === 0; + + // Category colSpan map + const tabsByCategory = useMemo( + () => + tabs.reduce((map, tab) => { + const existing = map.get(tab.categoryId) ?? []; + return map.set(tab.categoryId, [...existing, tab]); + }, new Map()), + [tabs], + ); + + return ( +
+ {/* Toolbar */} +
+ {canManageWeights && ( + + )} + setTreatUngradedAsZero(e.target.checked)} + /> + } + label={t(translations.treatUngradedAsZero)} + /> +
+ + {/* Empty state */} + {allWeightsZero && ( + + + {t(translations.emptyStateTitle)} + + + {t(translations.emptyStateBody)} + + + )} + + {/* Table */} + + + + {/* Row 1: categories + Total */} + + + {t(translations.name)} + + {categories.map((cat) => { + const catTabs = tabsByCategory.get(cat.id) ?? []; + return ( + + {cat.title} + + ); + })} + + {t(translations.total)} + + + + {/* Row 2: tab titles */} + + + {tabs.map((tab) => ( + + {tab.title} + + ))} + + + + {/* Row 3: weight subheaders */} + + + {tabs.map((tab) => ( + + {t(translations.weightSubheader, { + weight: tab.gradebookWeight ?? 0, + })} + + ))} + + {weightSum !== 100 ? ( + + + {t(translations.totalSubheader, { sum: weightSum })} +   + + + + ) : ( + t(translations.totalSubheader, { sum: weightSum }) + )} + + + + + + {rows.map((row) => ( + + {row.name} + {tabs.map((tab) => ( + + {formatPct(row.subtotalsByTabId[tab.id])} + + ))} + {formatPct(row.total)} + + ))} + +
+
+ + setDialogOpen(false)} + open={dialogOpen} + tabs={tabs} + /> +
+ ); +}; + +export default GradebookWeightedTable; diff --git a/client/app/bundles/course/gradebook/computeWeighted.ts b/client/app/bundles/course/gradebook/computeWeighted.ts new file mode 100644 index 0000000000..714a9f961c --- /dev/null +++ b/client/app/bundles/course/gradebook/computeWeighted.ts @@ -0,0 +1,96 @@ +import type { + AssessmentData, + SubmissionData, + TabData, +} from 'types/course/gradebook'; + +interface SubtotalArgs { + studentId: number; + tab: TabData; + assessments: AssessmentData[]; + submissions: SubmissionData[]; + treatUngradedAsZero: boolean; +} + +export const computeTabSubtotal = ({ + studentId, + tab, + assessments, + submissions, + treatUngradedAsZero, +}: SubtotalArgs): number | null => { + const tabAssessments = assessments.filter((a) => a.tabId === tab.id); + if (tabAssessments.length === 0) return null; + + let numerator = 0; + let denominator = 0; + tabAssessments.forEach((a) => { + const grade = submissions.find( + (s) => s.studentId === studentId && s.assessmentId === a.id, + )?.grade; + if (grade != null) { + numerator += grade; + denominator += a.maxGrade; + } else if (treatUngradedAsZero) { + denominator += a.maxGrade; + } + }); + return denominator > 0 ? numerator / denominator : null; +}; + +interface TotalArgs { + studentId: number; + tabs: TabData[]; + assessments: AssessmentData[]; + submissions: SubmissionData[]; + treatUngradedAsZero: boolean; +} + +export const computeStudentTotal = ({ + studentId, + tabs, + assessments, + submissions, + treatUngradedAsZero, +}: TotalArgs): number | null => { + // Build per-student O(1) lookup map to avoid O(n) scans per assessment + const gradeByAssessmentId = new Map(); + submissions.forEach((s) => { + if (s.studentId === studentId) + gradeByAssessmentId.set(s.assessmentId, s.grade); + }); + + const { weightedSum, weightSum } = tabs.reduce( + (acc, tab) => { + const weight = tab.gradebookWeight ?? 0; + if (weight <= 0) return acc; + + const tabAssessments = assessments.filter((a) => a.tabId === tab.id); + if (tabAssessments.length === 0) return acc; + + let numerator = 0; + let denominator = 0; + tabAssessments.forEach((a) => { + const grade = gradeByAssessmentId.get(a.id); + if (grade != null) { + numerator += grade; + denominator += a.maxGrade; + } else if (treatUngradedAsZero) { + denominator += a.maxGrade; + } + }); + + const sub = denominator > 0 ? numerator / denominator : null; + if (sub == null) return acc; + return { + weightedSum: acc.weightedSum + weight * sub, + weightSum: acc.weightSum + weight, + }; + }, + { weightedSum: 0, weightSum: 0 }, + ); + return weightSum > 0 ? weightedSum / weightSum : null; +}; + +export const sumWeights = (tabs: TabData[]): number => + tabs.reduce((acc, t) => acc + (t.gradebookWeight ?? 0), 0); diff --git a/client/app/bundles/course/gradebook/operations.ts b/client/app/bundles/course/gradebook/operations.ts index 35790580ed..6bf1426ad9 100644 --- a/client/app/bundles/course/gradebook/operations.ts +++ b/client/app/bundles/course/gradebook/operations.ts @@ -1,4 +1,5 @@ import type { Operation } from 'store'; +import type { UpdateWeightsPayload } from 'types/course/gradebook'; import CourseAPI from 'api/course'; @@ -9,4 +10,11 @@ const fetchGradebook = (): Operation => async (dispatch) => { dispatch(actions.saveGradebook(response.data)); }; +export const updateGradebookWeights = + (weights: UpdateWeightsPayload['weights']): Operation => + async (dispatch) => { + const response = await CourseAPI.gradebook.updateWeights({ weights }); + dispatch(actions.updateTabWeights(response.data?.weights ?? weights)); + }; + export default fetchGradebook; diff --git a/client/app/bundles/course/gradebook/pages/GradebookIndex/index.tsx b/client/app/bundles/course/gradebook/pages/GradebookIndex/index.tsx index cc140af58f..d8bf469834 100644 --- a/client/app/bundles/course/gradebook/pages/GradebookIndex/index.tsx +++ b/client/app/bundles/course/gradebook/pages/GradebookIndex/index.tsx @@ -3,6 +3,8 @@ import { defineMessages } from 'react-intl'; import { useParams } from 'react-router-dom'; import { PeopleAlt } from '@mui/icons-material'; import { Typography } from '@mui/material'; +import ToggleButton from '@mui/material/ToggleButton'; +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; import Page from 'lib/components/core/layouts/Page'; import LoadingIndicator from 'lib/components/core/LoadingIndicator'; @@ -12,14 +14,17 @@ import useTranslation from 'lib/hooks/useTranslation'; import { useCourseContext } from '../../../container/CourseLoader'; import GradebookTable from '../../components/GradebookTable'; +import GradebookWeightedTable from '../../components/GradebookWeightedTable'; import fetchGradebook from '../../operations'; import { getAssessments, + getCanManageWeights, getCategories, getGamificationEnabled, getStudents, getSubmissions, getTabs, + getWeightedViewEnabled, } from '../../selectors'; const translations = defineMessages({ @@ -39,6 +44,14 @@ const translations = defineMessages({ id: 'course.gradebook.GradebookIndex.noStudentsHint', defaultMessage: 'Grades will appear here once students join the course.', }, + allAssessments: { + id: 'course.gradebook.GradebookIndex.allAssessments', + defaultMessage: 'All assessments', + }, + byWeight: { + id: 'course.gradebook.GradebookIndex.byWeight', + defaultMessage: 'By weight', + }, }); const GradebookIndex: FC = () => { @@ -48,6 +61,7 @@ const GradebookIndex: FC = () => { const { courseId: courseIdParam } = useParams(); const courseId = parseInt(courseIdParam!, 10); const [isLoading, setIsLoading] = useState(true); + const [viewMode, setViewMode] = useState<'all' | 'weighted'>('all'); const assessments = useAppSelector(getAssessments); const categories = useAppSelector(getCategories); @@ -55,6 +69,8 @@ const GradebookIndex: FC = () => { const students = useAppSelector(getStudents); const submissions = useAppSelector(getSubmissions); const gamificationEnabled = useAppSelector(getGamificationEnabled); + const weightedViewEnabled = useAppSelector(getWeightedViewEnabled); + const canManageWeights = useAppSelector(getCanManageWeights); useEffect(() => { dispatch(fetchGradebook()) @@ -62,6 +78,10 @@ const GradebookIndex: FC = () => { .catch(() => toast.error(t(translations.fetchFailure))); }, [dispatch]); + useEffect(() => { + if (!weightedViewEnabled) setViewMode('all'); + }, [weightedViewEnabled]); + let content: JSX.Element; if (isLoading) { content = ; @@ -78,17 +98,54 @@ const GradebookIndex: FC = () => { ); } else { + const viewToggle = weightedViewEnabled ? ( +
+ { + if (v) setViewMode(v); + }} + size="small" + value={viewMode} + > + + {t(translations.allAssessments)} + + + {t(translations.byWeight)} + + +
+ ) : null; + + const tableView = + viewMode === 'weighted' && weightedViewEnabled ? ( + + ) : ( + + ); + content = ( - + <> + {viewToggle} + {tableView} + ); } diff --git a/client/app/bundles/course/gradebook/selectors.ts b/client/app/bundles/course/gradebook/selectors.ts index fbe62e2611..8541e471fd 100644 --- a/client/app/bundles/course/gradebook/selectors.ts +++ b/client/app/bundles/course/gradebook/selectors.ts @@ -22,3 +22,9 @@ export const getGamificationEnabled = ( state: AppState, ): GradebookState['gamificationEnabled'] => getLocalState(state).gamificationEnabled; + +export const getWeightedViewEnabled = (state: AppState): boolean => + getLocalState(state).weightedViewEnabled; + +export const getCanManageWeights = (state: AppState): boolean => + getLocalState(state).canManageWeights; diff --git a/client/app/bundles/course/gradebook/store.ts b/client/app/bundles/course/gradebook/store.ts index 00e3291032..8289312b4f 100644 --- a/client/app/bundles/course/gradebook/store.ts +++ b/client/app/bundles/course/gradebook/store.ts @@ -1,5 +1,8 @@ import { produce } from 'immer'; -import type { GradebookData } from 'types/course/gradebook'; +import type { + GradebookData, + UpdateWeightsPayload, +} from 'types/course/gradebook'; import type { AssessmentData, @@ -10,6 +13,7 @@ import type { } from './types'; const SAVE_GRADEBOOK = 'course/gradebook/SAVE_GRADEBOOK'; +const UPDATE_TAB_WEIGHTS = 'course/gradebook/UPDATE_TAB_WEIGHTS'; interface GradebookState { categories: CategoryData[]; @@ -18,6 +22,8 @@ interface GradebookState { students: StudentData[]; submissions: SubmissionData[]; gamificationEnabled: boolean; + weightedViewEnabled: boolean; + canManageWeights: boolean; } interface SaveGradebookAction { @@ -25,6 +31,11 @@ interface SaveGradebookAction { payload: GradebookData; } +interface UpdateTabWeightsAction { + type: typeof UPDATE_TAB_WEIGHTS; + payload: UpdateWeightsPayload['weights']; +} + const initialState: GradebookState = { categories: [], tabs: [], @@ -32,18 +43,36 @@ const initialState: GradebookState = { students: [], submissions: [], gamificationEnabled: false, + weightedViewEnabled: false, + canManageWeights: false, }; const reducer = produce( - (draft: GradebookState, action: SaveGradebookAction) => { + ( + draft: GradebookState, + action: SaveGradebookAction | UpdateTabWeightsAction, + ) => { switch (action.type) { case SAVE_GRADEBOOK: { - draft.categories = action.payload.categories; - draft.tabs = action.payload.tabs; - draft.assessments = action.payload.assessments; - draft.students = action.payload.students; - draft.submissions = action.payload.submissions; - draft.gamificationEnabled = action.payload.gamificationEnabled; + const payload = (action as SaveGradebookAction).payload; + draft.categories = payload.categories; + draft.tabs = payload.tabs; + draft.assessments = payload.assessments; + draft.students = payload.students; + draft.submissions = payload.submissions; + draft.gamificationEnabled = payload.gamificationEnabled; + draft.weightedViewEnabled = payload.weightedViewEnabled; + draft.canManageWeights = payload.canManageWeights; + break; + } + case UPDATE_TAB_WEIGHTS: { + const weights = (action as UpdateTabWeightsAction).payload; + weights.forEach(({ tabId, weight }) => { + const tab = draft.tabs.find((t) => t.id === tabId); + if (tab) { + tab.gradebookWeight = weight; + } + }); break; } default: @@ -58,6 +87,12 @@ export const actions = { type: SAVE_GRADEBOOK, payload: data, }), + updateTabWeights: ( + weights: UpdateWeightsPayload['weights'], + ): UpdateTabWeightsAction => ({ + type: UPDATE_TAB_WEIGHTS, + payload: weights, + }), }; export default reducer; diff --git a/client/app/bundles/course/gradebook/types.ts b/client/app/bundles/course/gradebook/types.ts index f94aa7bf9c..b91689df87 100644 --- a/client/app/bundles/course/gradebook/types.ts +++ b/client/app/bundles/course/gradebook/types.ts @@ -5,4 +5,5 @@ export type { StudentData, SubmissionData, TabData, + UpdateWeightsPayload, } from 'types/course/gradebook'; diff --git a/client/app/types/course/gradebook.ts b/client/app/types/course/gradebook.ts index 613df6b1cd..d041add93b 100644 --- a/client/app/types/course/gradebook.ts +++ b/client/app/types/course/gradebook.ts @@ -7,6 +7,7 @@ export interface TabData { id: number; title: string; categoryId: number; + gradebookWeight?: number; } export interface AssessmentData { @@ -37,4 +38,10 @@ export interface GradebookData { students: StudentData[]; submissions: SubmissionData[]; gamificationEnabled: boolean; + weightedViewEnabled: boolean; + canManageWeights: boolean; +} + +export interface UpdateWeightsPayload { + weights: { tabId: number; weight: number }[]; } diff --git a/client/locales/en.json b/client/locales/en.json index ce7ee7aee0..54c1aae06b 100644 --- a/client/locales/en.json +++ b/client/locales/en.json @@ -1,4 +1,7 @@ { + "2kgbTg": { + "defaultMessage": "Role-Playing Assessment" + }, "announcements.GlobalAnnouncementIndex.fetchAnnouncementsFailure": { "defaultMessage": "Unable to fetch announcements" }, @@ -29,6 +32,9 @@ "app.DashboardPage.yourCourses": { "defaultMessage": "Your Courses" }, + "app.ErrorPage.courseSuspended": { + "defaultMessage": "This course is suspended." + }, "app.ErrorPage.error": { "defaultMessage": "KABOOM, a meteor has just crashed." }, @@ -59,14 +65,11 @@ "app.ErrorPage.notFoundSubtitle": { "defaultMessage": "Check if you've typed the correct address, try again later, or go back home." }, - "app.ErrorPage.userSuspended": { - "defaultMessage": "Your access to this course has been suspended." - }, "app.ErrorPage.suspendedSubtitle": { "defaultMessage": "Please contact your instructors or the course staff." }, - "app.ErrorPage.courseSuspended": { - "defaultMessage": "This course is suspended." + "app.ErrorPage.userSuspended": { + "defaultMessage": "Your access to this course has been suspended." }, "app.Footer.contactUs": { "defaultMessage": "Contact Us" @@ -80,12 +83,12 @@ "app.Footer.instructorsGuide": { "defaultMessage": "Instructors' Guide" }, - "app.Footer.reportIssue": { - "defaultMessage": "Report an Issue" - }, "app.Footer.privacyPolicy": { "defaultMessage": "Privacy Policy" }, + "app.Footer.reportIssue": { + "defaultMessage": "Report an Issue" + }, "app.Footer.termsOfService": { "defaultMessage": "Terms of Service" }, @@ -101,6 +104,9 @@ "assessment.attemptLoader.errorAttemptingAssessment": { "defaultMessage": "An error occurred while attempting this assessment. Try again later." }, + "bW7B87": { + "defaultMessage": "New" + }, "client.video.attemptLoader.errorWatchVideo": { "defaultMessage": "An error occurred while attempting to watch this video. Try again later." }, @@ -263,23 +269,23 @@ "course.achievement.AchievementAward.AchievementAwardManager.saveChanges": { "defaultMessage": "Save Changes" }, + "course.achievement.AchievementAward.AchievementAwardSummary.awardedStudents": { + "defaultMessage": "Awarded Students" + }, "course.achievement.AchievementAward.AchievementAwardSummary.name": { "defaultMessage": "Name" }, - "course.achievement.AchievementAward.AchievementAwardSummary.userType": { - "defaultMessage": "User Type" + "course.achievement.AchievementAward.AchievementAwardSummary.normalStudent": { + "defaultMessage": "Normal Student" }, - "course.achievement.AchievementAward.AchievementAwardSummary.awardedStudents": { - "defaultMessage": "Awarded Students" + "course.achievement.AchievementAward.AchievementAwardSummary.phantomStudent": { + "defaultMessage": "Phantom Student" }, "course.achievement.AchievementAward.AchievementAwardSummary.revokedStudents": { "defaultMessage": "Revoked Students" }, - "course.achievement.AchievementAward.AchievementAwardSummary.phantomStudent": { - "defaultMessage": "Phantom Student" - }, - "course.achievement.AchievementAward.AchievementAwardSummary.normalStudent": { - "defaultMessage": "Normal Student" + "course.achievement.AchievementAward.AchievementAwardSummary.userType": { + "defaultMessage": "User Type" }, "course.achievement.AchievementAward.awardAchievement": { "defaultMessage": "Award Achievement" @@ -353,26 +359,26 @@ "course.achievement.AchievementShow.studentsWithAchievement": { "defaultMessage": "Students with this achievement" }, - "course.achievement.AchievementTable.noAchievement": { - "defaultMessage": "No achievement" + "course.achievement.AchievementTable.actions": { + "defaultMessage": "Actions" }, "course.achievement.AchievementTable.badge": { "defaultMessage": "Badge" }, - "course.achievement.AchievementTable.title": { - "defaultMessage": "Title" - }, "course.achievement.AchievementTable.description": { "defaultMessage": "Description" }, - "course.achievement.AchievementTable.requirements": { - "defaultMessage": "Requirements" + "course.achievement.AchievementTable.noAchievement": { + "defaultMessage": "No achievement" }, "course.achievement.AchievementTable.published": { "defaultMessage": "Published" }, - "course.achievement.AchievementTable.actions": { - "defaultMessage": "Actions" + "course.achievement.AchievementTable.requirements": { + "defaultMessage": "Requirements" + }, + "course.achievement.AchievementTable.title": { + "defaultMessage": "Title" }, "course.achievement.AchievementsIndex.achievements": { "defaultMessage": "Achievements" @@ -515,46 +521,6 @@ "course.admin.AssessmentSettings.toTab": { "defaultMessage": "to {tab}" }, - "course.admin.CodaveriSettings.codaveriModel": { - "defaultMessage": "Model" - }, - "course.admin.CodaveriSettings.codaveriModelDescription": { - "defaultMessage": "The AI model used by Codaveri to generate help conversations with students for programming questions." - }, - "course.admin.CodaveriSettings.codaveriSystemPromptDescription": { - "defaultMessage": - "You may customize the behavior of the Codaveri model by providing instructions here. {br} When assisting students, these instructions will be followed in addition to any you have set on the question itself.{br}To reference question-specific details, you may use the following variables within the prompt, writing them with brackets as shown below:" - }, - "course.admin.CodaveriSettings.codaveriSystemPromptProblemDescriptionLine": { - "defaultMessage": "{problemDescriptionVar} : The full description of the coding problem." - }, - "course.admin.CodaveriSettings.codaveriSystemPromptStudentFilePathsLine": { - "defaultMessage": "{studentFilePathsVar} : A comma-separated list of file paths the student is working on." - }, - "course.admin.CodaveriSettings.codaveriSettings": { - "defaultMessage": "Codaveri settings" - }, - "course.admin.CodaveriSettings.codaveriSettingsSubtitle": { - "defaultMessage": "This is currently an experimental feature. Codaveri provides code evaluation and automated code feedback services for students' codes." - }, - "course.admin.CodaveriSettings.feedbackWorkflow": { - "defaultMessage": "Automatic Post-Submission Comments" - }, - "course.admin.CodaveriSettings.feedbackWorkflowDescription": { - "defaultMessage": "When a submission with programming question is finalised," - }, - "course.admin.CodaveriSettings.feedbackWorkflowNone": { - "defaultMessage": "Generate no feedback" - }, - "course.admin.CodaveriSettings.feedbackWorkflowDraft": { - "defaultMessage": "Generate feedback as a draft requiring approval from staff" - }, - "course.admin.CodaveriSettings.feedbackWorkflowPublish": { - "defaultMessage": "Publish feedback directly to student" - }, - "course.admin.CodaveriSettings.error": { - "defaultMessage": "An error occurred while updating the codaveri setting." - }, "course.admin.CodaveriSettings.Some": { "defaultMessage": "Some" }, @@ -570,32 +536,80 @@ "course.admin.CodaveriSettings.codaveriEngineDescription": { "defaultMessage": "Type of codaveri engine used to generate programming code feedback" }, + "course.admin.CodaveriSettings.codaveriEvaluatorSettings": { + "defaultMessage": "Codaveri Evaluator" + }, + "course.admin.CodaveriSettings.codaveriModel": { + "defaultMessage": "Model" + }, + "course.admin.CodaveriSettings.codaveriModelDescription": { + "defaultMessage": "The AI model used by Codaveri to generate help conversations with students for programming questions." + }, "course.admin.CodaveriSettings.codaveriOverrideSystemPrompt": { "defaultMessage": "Use a custom system prompt" }, "course.admin.CodaveriSettings.codaveriOverrideSystemPromptDescription": { "defaultMessage": "When assisting students, these instructions will be followed in addition to any you have set on the question itself. To reference question-specific details, you may use these variables within the prompt, writing them with brackets as shown below:" }, + "course.admin.CodaveriSettings.codaveriSettings": { + "defaultMessage": "Codaveri settings" + }, + "course.admin.CodaveriSettings.codaveriSettingsSubtitle": { + "defaultMessage": "This is currently an experimental feature. Codaveri provides code evaluation and automated code feedback services for students' codes." + }, "course.admin.CodaveriSettings.codaveriSystemPrompt": { "defaultMessage": "System Prompt" }, + "course.admin.CodaveriSettings.codaveriSystemPromptDescription": { + "defaultMessage": "The Codaveri system prompt controls AI behavior when interacting with students." + }, + "course.admin.CodaveriSettings.codaveriSystemPromptProblemDescriptionLine": { + "defaultMessage": "{problemDescriptionVar} : The full description of the coding problem." + }, + "course.admin.CodaveriSettings.codaveriSystemPromptStudentFilePathsLine": { + "defaultMessage": "{studentFilePathsVar} : A comma-separated list of file paths the student is working on." + }, "course.admin.CodaveriSettings.codaveriUseDefaultSystemPrompt": { "defaultMessage": "Use the default system prompt" }, + "course.admin.CodaveriSettings.enableDisableButton": { + "defaultMessage": "{enabled, select, true {Enable} other {Disable}}" + }, + "course.admin.CodaveriSettings.enableDisableEvaluator": { + "defaultMessage": "{enabled, select, true {Enable } other {Disable }} Codaveri Evaluator for {questionCount} programming questions in {title}?" + }, + "course.admin.CodaveriSettings.enableDisableEvaluatorDescription": { + "defaultMessage": "{questionCount} programming questions in this {type} will use {enabled, select, true {Codaveri } other {Default }} evaluator" + }, + "course.admin.CodaveriSettings.enableDisableLiveFeedback": { + "defaultMessage": "{enabled, select, true {Enable } other {Disable }} Get Help for {questionCount} programming questions in {title}?" + }, + "course.admin.CodaveriSettings.errorOccurredWhenUpdatingCodaveriEvaluatorSettings": { + "defaultMessage": "An error occurred while updating the codaveri evaluator settings." + }, + "course.admin.CodaveriSettings.errorOccurredWhenUpdatingLiveFeedbackSettings": { + "defaultMessage": "An error occurred while updating the Get Help settings." + }, "course.admin.CodaveriSettings.evaluatorUpdateSuccess": { "defaultMessage": "{question} is now using {evaluator} evaluator" }, "course.admin.CodaveriSettings.expandAll": { "defaultMessage": "Expand All Questions" }, - "course.admin.CodaveriSettings.programmingQuestionSettings": { - "defaultMessage": "Programming Question Settings" + "course.admin.CodaveriSettings.feedbackWorkflow": { + "defaultMessage": "Automatic Post-Submission Comments" }, - "course.admin.CodaveriSettings.programmingQuestionSettingsSubtitle": { - "defaultMessage": "Enable/disable Codaveri as evaluator for programming questions in various assessments." + "course.admin.CodaveriSettings.feedbackWorkflowDescription": { + "defaultMessage": "When a submission with programming question is finalised," }, - "course.admin.CodaveriSettings.succesfulUpdateAllEvaluator": { - "defaultMessage": "Successfully updated all questions to use {evaluator} evaluator" + "course.admin.CodaveriSettings.feedbackWorkflowDraft": { + "defaultMessage": "Generate feedback as a draft requiring approval from staff" + }, + "course.admin.CodaveriSettings.feedbackWorkflowNone": { + "defaultMessage": "Generate no feedback" + }, + "course.admin.CodaveriSettings.feedbackWorkflowPublish": { + "defaultMessage": "Publish feedback directly to student" }, "course.admin.CodaveriSettings.getHelpUsageLimit": { "defaultMessage": "Limit Get Help messages per student" @@ -603,35 +617,23 @@ "course.admin.CodaveriSettings.getHelpUsageLimitDescription": { "defaultMessage": "If enabled, students will only be able to send a limited number of messages per question. Students will be able to see this limit and how many messages they have left." }, - "course.admin.CodaveriSettings.maxGetHelpUserMessages": { - "defaultMessage": "Maximum messages per question" - }, - "course.admin.CodaveriSettings.errorOccurredWhenUpdatingCodaveriEvaluatorSettings": { - "defaultMessage": "An error occurred while updating the codaveri evaluator settings." - }, - "course.admin.CodaveriSettings.codaveriEvaluatorSettings": { - "defaultMessage": "Codaveri Evaluator" + "course.admin.CodaveriSettings.liveFeedbackEnabledUpdateSuccess": { + "defaultMessage": "Get Help for {question} is now {liveFeedbackEnabled, select, true {enabled} other {disabled}}" }, "course.admin.CodaveriSettings.liveFeedbackSettings": { "defaultMessage": "Get Help" }, - "course.admin.CodaveriSettings.errorOccurredWhenUpdatingLiveFeedbackSettings": { - "defaultMessage": "An error occurred while updating the Get Help settings." - }, - "course.admin.CodaveriSettings.enableDisableButton": { - "defaultMessage": "{enabled, select, true {Enable} other {Disable}}" - }, - "course.admin.CodaveriSettings.enableDisableEvaluator": { - "defaultMessage": "{enabled, select, true {Enable } other {Disable }} Codaveri Evaluator for {questionCount} programming questions in {title}?" + "course.admin.CodaveriSettings.maxGetHelpUserMessages": { + "defaultMessage": "Maximum messages per question" }, - "course.admin.CodaveriSettings.enableDisableLiveFeedback": { - "defaultMessage": "{enabled, select, true {Enable } other {Disable }} Get Help for {questionCount} programming questions in {title}?" + "course.admin.CodaveriSettings.programmingQuestionSettings": { + "defaultMessage": "Programming Question Settings" }, - "course.admin.CodaveriSettings.enableDisableEvaluatorDescription": { - "defaultMessage": "{questionCount} programming questions in this {type} will use {enabled, select, true {Codaveri } other {Default }} evaluator" + "course.admin.CodaveriSettings.programmingQuestionSettingsSubtitle": { + "defaultMessage": "Enable/disable Codaveri as evaluator for programming questions in various assessments." }, - "course.admin.CodaveriSettings.liveFeedbackEnabledUpdateSuccess": { - "defaultMessage": "Get Help for {question} is now {liveFeedbackEnabled, select, true {enabled} other {disabled}}" + "course.admin.CodaveriSettings.succesfulUpdateAllEvaluator": { + "defaultMessage": "Successfully updated all questions to use {evaluator} evaluator" }, "course.admin.CodaveriSettings.successfulUpdateAllLiveFeedbackEnabled": { "defaultMessage": "Successfully {liveFeedbackEnabled, select, true {enabled} other {disabled}} Get Help for all questions" @@ -648,9 +650,15 @@ "course.admin.ComponentSettings.errorOccurredWhenUpdatingComponents": { "defaultMessage": "An error occurred while updating the component settings." }, + "course.admin.ComponentSettings.settingUpComponent": { + "defaultMessage": "Setting up component for this course" + }, "course.admin.CourseSettings.allowUsersToSendEnrolmentRequests": { "defaultMessage": "Allow users to send enrolment requests" }, + "course.admin.CourseSettings.autoApproveEnrolmentRequests": { + "defaultMessage": "Automatically approve enrolment requests" + }, "course.admin.CourseSettings.clearChanges": { "defaultMessage": "Clear changes" }, @@ -678,6 +686,12 @@ "course.admin.CourseSettings.courseSettings": { "defaultMessage": "Course settings" }, + "course.admin.CourseSettings.courseSuspensionMessage": { + "defaultMessage": "Course suspension message" + }, + "course.admin.CourseSettings.courseSuspensionMessageDescription": { + "defaultMessage": "This message will be shown to users while this course is suspended. Leave blank to show a default message." + }, "course.admin.CourseSettings.daysInAdvance": { "defaultMessage": "Days in advance" }, @@ -765,23 +779,14 @@ "course.admin.CourseSettings.stragglersDescription": { "defaultMessage": "Leave no one behind; subsequent closing reference timings will be pushed back if students complete their assessments late." }, - "course.admin.CourseSettings.suspension": { - "defaultMessage": "Access suspension" - }, "course.admin.CourseSettings.suspendCourse": { "defaultMessage": "Suspend course" }, "course.admin.CourseSettings.suspendCourseDescription": { "defaultMessage": "A suspended course is inaccessible to all students. Instructors can still access the course and all student data will be retained." }, - "course.admin.CourseSettings.unsuspendCourse": { - "defaultMessage": "Unsuspend course" - }, - "course.admin.CourseSettings.courseSuspensionMessage": { - "defaultMessage": "Course suspension message" - }, - "course.admin.CourseSettings.courseSuspensionMessageDescription": { - "defaultMessage": "This message will be shown to users while this course is suspended. Leave blank to show a default message." + "course.admin.CourseSettings.suspendCourseFailure": { + "defaultMessage": "An error occurred while suspending this course." }, "course.admin.CourseSettings.suspendCoursePromptText": { "defaultMessage": "Are you sure you want to suspend this course? All students will not be able to access it until it is unsuspended." @@ -789,20 +794,8 @@ "course.admin.CourseSettings.suspendCourseSuccess": { "defaultMessage": "This course has been suspended." }, - "course.admin.CourseSettings.suspendCourseFailure": { - "defaultMessage": "An error occurred while suspending this course." - }, - "course.admin.CourseSettings.unsuspendCourseSuccess": { - "defaultMessage": "This course has been unsuspended." - }, - "course.admin.CourseSettings.unsuspendCourseFailure": { - "defaultMessage": "An error occurred while unsuspending this course." - }, - "course.admin.CourseSettings.userSuspensionMessage": { - "defaultMessage": "User suspension message" - }, - "course.admin.CourseSettings.userSuspensionMessageDescription": { - "defaultMessage": "This message will be shown to individual users whose access to this course has been suspended. Leave blank to show a default message." + "course.admin.CourseSettings.suspension": { + "defaultMessage": "Access suspension" }, "course.admin.CourseSettings.timeSettings": { "defaultMessage": "Time settings" @@ -813,12 +806,27 @@ "course.admin.CourseSettings.titleRequired": { "defaultMessage": "Course name is required." }, + "course.admin.CourseSettings.unsuspendCourse": { + "defaultMessage": "Unsuspend course" + }, + "course.admin.CourseSettings.unsuspendCourseFailure": { + "defaultMessage": "An error occurred while unsuspending this course." + }, + "course.admin.CourseSettings.unsuspendCourseSuccess": { + "defaultMessage": "This course has been unsuspended." + }, "course.admin.CourseSettings.uploadANewImage": { "defaultMessage": "Choose a new image" }, "course.admin.CourseSettings.uploadingLogo": { "defaultMessage": "Uploading your new logo..." }, + "course.admin.CourseSettings.userSuspensionMessage": { + "defaultMessage": "User suspension message" + }, + "course.admin.CourseSettings.userSuspensionMessageDescription": { + "defaultMessage": "This message will be shown to individual users whose access to this course has been suspended. Leave blank to show a default message." + }, "course.admin.CourseSettingst.confirmDeletePlaceholder": { "defaultMessage": "This is your last chance to go back!" }, @@ -873,6 +881,15 @@ "course.admin.ForumsSettings.markPostAsAnswerSetting": { "defaultMessage": "User who can mark a post as answer" }, + "course.admin.GradebookSettings.gradebookSettings": { + "defaultMessage": "Gradebook settings" + }, + "course.admin.GradebookSettings.weightedViewEnabled": { + "defaultMessage": "Enable weighted grade view" + }, + "course.admin.GradebookSettings.weightedViewEnabledHint": { + "defaultMessage": "Enables a \"By weight\" view in the gradebook where staff can configure per-tab weights and see a weighted Total column." + }, "course.admin.LeaderboardSettings.displayUserCount": { "defaultMessage": "Display user count" }, @@ -925,7 +942,10 @@ "defaultMessage": "Component Item Settings" }, "course.admin.LessonPlanSettings.lessonPlanItemSettings": { - "defaultMessage": "Lesson Plan Item Settings" + "defaultMessage": "Item Settings" + }, + "course.admin.LessonPlanSettings.lessonPlanSettings": { + "defaultMessage": "Lesson Plan Settings" }, "course.admin.LessonPlanSettings.noLessonPlanItems": { "defaultMessage": "There are no lesson plan items to configure for lesson plan display." @@ -942,11 +962,14 @@ "course.admin.MaterialSettings.materialsSettings": { "defaultMessage": "Materials settings" }, + "course.admin.NotificationSettings.component": { + "defaultMessage": "Component" + }, "course.admin.NotificationSettings.description": { "defaultMessage": "Description" }, "course.admin.NotificationSettings.emailSettings": { - "defaultMessage": "Email Settings" + "defaultMessage": "Email settings" }, "course.admin.NotificationSettings.noEmailSettings": { "defaultMessage": "None of the enabled components have email settings." @@ -1059,28 +1082,148 @@ "course.admin.NotificationSettings.updateSuccess": { "defaultMessage": "The email setting \"{setting}\" for {user} users has been {action}." }, - "course.admin.SidebarSettings.errorOccurredWhenUpdatingSidebar": { - "defaultMessage": "An error occurred while updating the sidebar ordering." + "course.admin.RagWiseSettings.ForumKnowledgeBaseSwitch.addFailure": { + "defaultMessage": "{forum} could not be added to knowledge base." }, - "course.admin.SidebarSettings.sidebarSettings": { - "defaultMessage": "Student's sidebar ordering" + "course.admin.RagWiseSettings.ForumKnowledgeBaseSwitch.addSuccess": { + "defaultMessage": "{forum} {n, plural, one {has} other {have}} been added to knowledge base." }, - "course.admin.SidebarSettings.sidebarSettingsSubtitle": { - "defaultMessage": "Drag and drop the sidebar items to rearrange." + "course.admin.RagWiseSettings.ForumKnowledgeBaseSwitch.pendingImport": { + "defaultMessage": "Please wait as your request to import forums into knowledge base is being processed. You may close this window while importing is in progress." }, - "course.admin.SidebarSettings.sidebarSettingsUpdated": { - "defaultMessage": "The new sidebar ordering has been applied. Refresh to see the latest changes." + "course.admin.RagWiseSettings.ForumKnowledgeBaseSwitch.removeFailure": { + "defaultMessage": "{forum} could not be removed from knowledge base." }, - "course.admin.VideosSettings.addATab": { - "defaultMessage": "Add a tab" + "course.admin.RagWiseSettings.ForumKnowledgeBaseSwitch.removeSuccess": { + "defaultMessage": "{forum} {n, plural, one {has} other {have}} been removed from knowledge base." }, - "course.admin.VideosSettings.deleteTabPromptAction": { - "defaultMessage": "Delete {title} tab" + "course.admin.RagWiseSettings.KnowledgeBaseSwitch.addFailure": { + "defaultMessage": "{material} could not be added to knowledge base." }, - "course.admin.VideosSettings.deleteTabPromptMessage": { - "defaultMessage": "Deleting this tab will delete all its associated videos and statistics. This action is irreversible." + "course.admin.RagWiseSettings.KnowledgeBaseSwitch.addSuccess": { + "defaultMessage": "{material} {n, plural, one {has} other {have}} been added to knowledge base." }, - "course.admin.VideosSettings.deleteTabPromptTitle": { + "course.admin.RagWiseSettings.KnowledgeBaseSwitch.pendingAdd": { + "defaultMessage": "Please wait as your request to add materials into knowledge base is being processed. You may close this window while adding is in progress." + }, + "course.admin.RagWiseSettings.KnowledgeBaseSwitch.removeFailure": { + "defaultMessage": "{material} could not be removed from knowledge base." + }, + "course.admin.RagWiseSettings.KnowledgeBaseSwitch.removeSuccess": { + "defaultMessage": "{material} {n, plural, one {has} other {have}} been removed from knowledge base." + }, + "course.admin.RagWiseSettings.expandAll": { + "defaultMessage": "Expand all {object}" + }, + "course.admin.RagWiseSettings.forumSectionSubtitle": { + "defaultMessage": "Manage the inclusion or exclusion of forum data from related courses in the knowledge base, allowing users to control its availability to the LLM for generating responses." + }, + "course.admin.RagWiseSettings.forumSectionTitle": { + "defaultMessage": "No related courses found." + }, + "course.admin.RagWiseSettings.knowledgeBaseStatusSettings": { + "defaultMessage": "Knowledge Base" + }, + "course.admin.RagWiseSettings.materialsSectionSubtitle": { + "defaultMessage": "Add/remove pdf/docx/ipynb/txt files in knowledge base, allowing users to control its availability to the LLM for generating responses." + }, + "course.admin.RagWiseSettings.materialsSectionTitle": { + "defaultMessage": "Materials" + }, + "course.admin.RagWiseSettings.ragWiseSettings": { + "defaultMessage": "RagWise settings" + }, + "course.admin.RagWiseSettings.ragWiseSettingsSubtitle": { + "defaultMessage": "This is currently an experimental feature. RagWise uses Retrieval-Augmented Generation to generate contextually aware responses to student's query on forum." + }, + "course.admin.RagWiseSettings.responseWorkflowAuto": { + "defaultMessage": "Automatically respond" + }, + "course.admin.RagWiseSettings.responseWorkflowDescription": { + "defaultMessage": "When students post a question on forum," + }, + "course.admin.RagWiseSettings.responseWorkflowDraft": { + "defaultMessage": "Always draft" + }, + "course.admin.RagWiseSettings.responseWorkflowDraftDescription": { + "defaultMessage": "Generated response will be drafted." + }, + "course.admin.RagWiseSettings.responseWorkflowHighTrust": { + "defaultMessage": "High trust" + }, + "course.admin.RagWiseSettings.responseWorkflowLowTrust": { + "defaultMessage": "Low trust" + }, + "course.admin.RagWiseSettings.responseWorkflowLowTrustDescription": { + "defaultMessage": "Generated response will be conditionally published with {trust}% trust." + }, + "course.admin.RagWiseSettings.responseWorkflowNoAuto": { + "defaultMessage": "Do not automatically respond" + }, + "course.admin.RagWiseSettings.responseWorkflowPublish": { + "defaultMessage": "Always publish" + }, + "course.admin.RagWiseSettings.responseWorkflowPublishDescription": { + "defaultMessage": "Generated response will be immediately published." + }, + "course.admin.RagWiseSettings.responseWorkflowTitle": { + "defaultMessage": "Automatic Forum Response" + }, + "course.admin.RagWiseSettings.roleplayCharacter": { + "defaultMessage": "Specified Character Prompt" + }, + "course.admin.RagWiseSettings.roleplayCharacterLabel": { + "defaultMessage": "Character prompt (Max 200 Characters)" + }, + "course.admin.RagWiseSettings.roleplayDeadpool": { + "defaultMessage": "You must always impersonate Deadpool character in all your responses." + }, + "course.admin.RagWiseSettings.roleplayDeadpoolLabel": { + "defaultMessage": "Deadpool" + }, + "course.admin.RagWiseSettings.roleplayDescription": { + "defaultMessage": "Customise character prompt to change how LLM response" + }, + "course.admin.RagWiseSettings.roleplayNormal": { + "defaultMessage": "" + }, + "course.admin.RagWiseSettings.roleplayNormalLabel": { + "defaultMessage": "No roleplay" + }, + "course.admin.RagWiseSettings.roleplaySubtitle": { + "defaultMessage": "Character that LLM will roleplay as in responses." + }, + "course.admin.RagWiseSettings.roleplayTitle": { + "defaultMessage": "Response Roleplay" + }, + "course.admin.RagWiseSettings.roleplayYoda": { + "defaultMessage": "You must always impersonate Master Yoda character in all your responses." + }, + "course.admin.RagWiseSettings.roleplayYodaLabel": { + "defaultMessage": "Master Yoda" + }, + "course.admin.SidebarSettings.errorOccurredWhenUpdatingSidebar": { + "defaultMessage": "An error occurred while updating the sidebar ordering." + }, + "course.admin.SidebarSettings.sidebarSettings": { + "defaultMessage": "Student's sidebar ordering" + }, + "course.admin.SidebarSettings.sidebarSettingsSubtitle": { + "defaultMessage": "Drag and drop the sidebar items to rearrange." + }, + "course.admin.SidebarSettings.sidebarSettingsUpdated": { + "defaultMessage": "The new sidebar ordering has been applied. Refresh to see the latest changes." + }, + "course.admin.VideosSettings.addATab": { + "defaultMessage": "Add a tab" + }, + "course.admin.VideosSettings.deleteTabPromptAction": { + "defaultMessage": "Delete {title} tab" + }, + "course.admin.VideosSettings.deleteTabPromptMessage": { + "defaultMessage": "Deleting this tab will delete all its associated videos and statistics. This action is irreversible." + }, + "course.admin.VideosSettings.deleteTabPromptTitle": { "defaultMessage": "Delete {title} tab?" }, "course.admin.VideosSettings.errorOccurredWhenCreatingTab": { @@ -1122,6 +1265,51 @@ "course.admin.courseSettings": { "defaultMessage": "Course Settings" }, + "course.admin.storiesSettings.autoCreateAccounts": { + "defaultMessage": "User accounts and chat rooms on Cikgo will automatically be created if they don't yet exist. Information shared with Cikgo is governed by our Privacy Policy and Cikgo's Privacy Policy." + }, + "course.admin.storiesSettings.integrationHint": { + "defaultMessage": "To integrate your course on Cikgo with this course, enter its integration key here. Here's what's going to happen once this course is integrated with Cikgo." + }, + "course.admin.storiesSettings.integrationSettings": { + "defaultMessage": "Integration settings" + }, + "course.admin.storiesSettings.learnTitle": { + "defaultMessage": "Learn page title" + }, + "course.admin.storiesSettings.leaveEmptyToUseDefaultTitle": { + "defaultMessage": "Leave empty to use the default \"Learn\" title." + }, + "course.admin.storiesSettings.onlyOwnersCanManage": { + "defaultMessage": "Only you, Owners, and Managers can configure the integration of this course with Cikgo." + }, + "course.admin.storiesSettings.pingError": { + "defaultMessage": "There was a problem connecting to Cikgo. You may try again at a later time." + }, + "course.admin.storiesSettings.publishTaskCompletions": { + "defaultMessage": "Student's submission statuses will be reflected in their chat rooms in Cikgo." + }, + "course.admin.storiesSettings.pushKey": { + "defaultMessage": "Integration key" + }, + "course.admin.storiesSettings.pushKeyError": { + "defaultMessage": "This integration key doesn't point to a valid course on Cikgo. Please check your settings on Cikgo and try again." + }, + "course.admin.storiesSettings.pushKeyHint": { + "defaultMessage": "Integration keys aren't strictly secretive, but should be handled in confidence." + }, + "course.admin.storiesSettings.pushKeyPointsToCourse": { + "defaultMessage": "This integration key points to {course} on Cikgo." + }, + "course.admin.storiesSettings.redirects": { + "defaultMessage": "When students access this course's root URL, they'll be redirected to the Learn page. The home page is still accessible from the sidebar." + }, + "course.admin.storiesSettings.storiesSettings": { + "defaultMessage": "Stories settings" + }, + "course.admin.storiesSettings.syncs": { + "defaultMessage": "Published assessments, videos, and surveys in this course will be available in and kept in sync with Cikgo as resources." + }, "course.announcement.AnnouncementsDisplay.searchBarPlaceholder": { "defaultMessage": "Search by title or content" }, @@ -1233,6 +1421,12 @@ "course.assessment.AssessmentForm.blockStudentViewingAfterSubmittedHint": { "defaultMessage": "Students will only be able to view their submissions after their grades have been published." }, + "course.assessment.AssessmentForm.blocksAccessesFromInvalidSUS": { + "defaultMessage": "Block accesses from browsers with invalid UA" + }, + "course.assessment.AssessmentForm.blocksAccessesFromInvalidSUSHint": { + "defaultMessage": "If enabled, examinees using browsers with invalid UA (does not contain the specified SUS below) will be blocked from accessing this assessment. Instructors can override access with the session unlock password. Heartbeats from an overridden browser session will be flagged as valid in the PulseGrid." + }, "course.assessment.AssessmentForm.bonusEndAt": { "defaultMessage": "Bonus ends at" }, @@ -1245,9 +1439,6 @@ "course.assessment.AssessmentForm.delayedGradePublicationHint": { "defaultMessage": "If enabled, gradings will not be immediately shown to students. To publish all gradings, you may click Publish Grades in the Submissions page." }, - "course.assessment.AssessmentForm.canEnableCodaveriInComponents": { - "defaultMessage": "Contact the course manager or owner to enable this feature in Components in the Course Settings." - }, "course.assessment.AssessmentForm.description": { "defaultMessage": "Description" }, @@ -1302,12 +1493,24 @@ "course.assessment.AssessmentForm.hasPersonalTimesHint": { "defaultMessage": "Timings for this item will be automatically adjusted for users based on learning rate." }, + "course.assessment.AssessmentForm.hasTimeLimit": { + "defaultMessage": "Automatically submit when timer ends" + }, + "course.assessment.AssessmentForm.hasTimeLimitHint": { + "defaultMessage": "When enabled, each submission will have its own timer and will automatically be finalised when its timer ends." + }, "course.assessment.AssessmentForm.hasToBeMoreThanMinInterval": { "defaultMessage": "Has to be greater than the minimum value." }, "course.assessment.AssessmentForm.hasToBeMoreThanValueMs": { "defaultMessage": "Has to be at least 3000 ms." }, + "course.assessment.AssessmentForm.hasToBeNumber": { + "defaultMessage": "Has to be valid number." + }, + "course.assessment.AssessmentForm.hasToBePositive": { + "defaultMessage": "Has to be positive." + }, "course.assessment.AssessmentForm.hasToBePositiveInteger": { "defaultMessage": "Has to be a positive integer less than 86,400,000 ms" }, @@ -1320,6 +1523,12 @@ "course.assessment.AssessmentForm.intervalHint": { "defaultMessage": "Controls how frequent heartbeats are sent from the students' browsers. Intervals are randomised between these two ranges." }, + "course.assessment.AssessmentForm.koditsuDisabledInCourse": { + "defaultMessage": "Please contact the Course Administrator to enable Koditsu Exam in Course Settings." + }, + "course.assessment.AssessmentForm.liveFeedback": { + "defaultMessage": "Get Help" + }, "course.assessment.AssessmentForm.maxInterval": { "defaultMessage": "Max interval" }, @@ -1329,9 +1538,18 @@ "course.assessment.AssessmentForm.minInterval": { "defaultMessage": "Min interval" }, + "course.assessment.AssessmentForm.minutes": { + "defaultMessage": "minute(s)" + }, "course.assessment.AssessmentForm.modeSwitchingHint": { "defaultMessage": "You can no longer change the grading mode because there are already submissions for this assessment." }, + "course.assessment.AssessmentForm.needSUSAndSessionUnlockPassword": { + "defaultMessage": "You need to specify a SUS and session unlock password to enable this." + }, + "course.assessment.AssessmentForm.noProgrammingQuestion": { + "defaultMessage": "You need to add at least one programming question that can be supported by Codaveri to allow enabling Get Help for this Assessment" + }, "course.assessment.AssessmentForm.noTestCaseChosenError": { "defaultMessage": "Select at least one type of test case" }, @@ -1356,29 +1574,32 @@ "course.assessment.AssessmentForm.personalisedTimelines": { "defaultMessage": "Personalised timelines" }, + "course.assessment.AssessmentForm.proctorWithKoditsu": { + "defaultMessage": "Proctor Exam using Koditsu" + }, "course.assessment.AssessmentForm.published": { "defaultMessage": "Published" }, "course.assessment.AssessmentForm.publishedHint": { "defaultMessage": "Everyone can see this assessment." }, + "course.assessment.AssessmentForm.questionsIncompatibleWithKoditsu": { + "defaultMessage": "Please make sure that all questions in this assessment is compatible with Koditsu before activating proctoring in Koditsu" + }, "course.assessment.AssessmentForm.secret": { "defaultMessage": "Secret UA Substring (SUS)" }, "course.assessment.AssessmentForm.secretHint": { - "defaultMessage": "If provided, Coursemology can automatically flag a connection as valid in PulseGrid if the examinee's User Agent (UA) contains this secret. Otherwise, connections will be flagged only by heartbeat intervals." + "defaultMessage": "If provided, the PulseGrid automatically checks if the examinee's browser's User Agent (UA) contains this secret, and marks connections that do not as invalid. This string is case-sensitive." }, "course.assessment.AssessmentForm.sessionPassword": { "defaultMessage": "Session unlock password" }, - "course.assessment.AssessmentForm.sessionPasswordHint": { - "defaultMessage": "Ideally, do NOT give this password to students." - }, "course.assessment.AssessmentForm.sessionProtection": { "defaultMessage": "Enable session protection" }, "course.assessment.AssessmentForm.sessionProtectionHint": { - "defaultMessage": "If enabled, students can only access their attempt once. Further access will require the session unlock password." + "defaultMessage": "If enabled, students can only access their attempt once. Further access will require the session unlock password. Ideally, do NOT give this password to students." }, "course.assessment.AssessmentForm.showEvaluation": { "defaultMessage": "Show evaluation test cases" @@ -1392,12 +1613,12 @@ "course.assessment.AssessmentForm.showMcqMrqSolution": { "defaultMessage": "Show MCQ/MRQ solution(s)" }, - "course.assessment.AssessmentForm.showRubricToStudents": { - "defaultMessage": "Show rubric breakdown to students" - }, "course.assessment.AssessmentForm.showPrivate": { "defaultMessage": "Show private test cases" }, + "course.assessment.AssessmentForm.showRubricToStudents": { + "defaultMessage": "Show rubric breakdown to students" + }, "course.assessment.AssessmentForm.singlePage": { "defaultMessage": "Single Page" }, @@ -1422,9 +1643,15 @@ "course.assessment.AssessmentForm.timeBonusExp": { "defaultMessage": "Time Bonus EXP" }, + "course.assessment.AssessmentForm.timeLimit": { + "defaultMessage": "Time Limit" + }, "course.assessment.AssessmentForm.title": { "defaultMessage": "Title" }, + "course.assessment.AssessmentForm.toggleLiveFeedbackDescription": { + "defaultMessage": "Enable Get Help feature for all programming questions" + }, "course.assessment.AssessmentForm.unavailableInAutograded": { "defaultMessage": "Unavailable in autograded assessments." }, @@ -1455,12 +1682,6 @@ "course.assessment.AssessmentForm.visibility": { "defaultMessage": "Visibility" }, - "course.assessment.AssessmentForm.toggleLiveFeedbackDescription": { - "defaultMessage": "{enabled, select, true {Enable} other {Disable}} Get Help feature for all programming questions" - }, - "course.assessment.AssessmentForm.noProgrammingQuestion": { - "defaultMessage": "You need to add at least one programming question that can be supported by Codaveri to allow enabling Get Help for this Assessment" - }, "course.assessment.FileManager.addFiles": { "defaultMessage": "Add Files" }, @@ -1506,9 +1727,24 @@ "course.assessment.edit.update": { "defaultMessage": "Save" }, + "course.assessment.generation.allFieldsLocked": { + "defaultMessage": "All fields are locked, so nothing can be generated." + }, "course.assessment.generation.confirmDeleteConversation": { "defaultMessage": "Are you sure you want to delete \"{title}\" and all its history items? THIS ACTION IS IRREVERSIBLE!" }, + "course.assessment.generation.createMode": { + "defaultMessage": "Create New" + }, + "course.assessment.generation.createModeTooltip": { + "defaultMessage": "Generate fresh questions from scratch" + }, + "course.assessment.generation.enhanceMode": { + "defaultMessage": "Enhance" + }, + "course.assessment.generation.enhanceModeTooltip": { + "defaultMessage": "Build upon your current question" + }, "course.assessment.generation.exportAction": { "defaultMessage": "Export" }, @@ -1518,140 +1754,212 @@ "course.assessment.generation.exportError": { "defaultMessage": "An error occurred in exporting this question: {error}" }, - "course.assessment.generation.lockTooltip": { - "defaultMessage": "Lock to prevent changes to this section" - }, - "course.assessment.generation.newTab": { - "defaultMessage": "New" - }, - "course.assessment.generation.openExportDialog": { - "defaultMessage": "Export" + "course.assessment.generation.generateError": { + "defaultMessage": "An error occurred generating question \"{title}\"." }, - "course.assessment.generation.resetConversation": { - "defaultMessage": "Reset" + "course.assessment.generation.generateMcqPage": { + "defaultMessage": "Generate Multiple Choice Question" }, - "course.assessment.generation.unlockTooltip": { - "defaultMessage": "Unlock to continue editing this section" + "course.assessment.generation.generateMrqPage": { + "defaultMessage": "Generate Multiple Response Question" }, - "course.assessment.generation.mrq.numberOfQuestionsField": { - "defaultMessage": "Number of Questions" + "course.assessment.generation.generateMultipleSuccess": { + "defaultMessage": "Successfully generated {count} questions!" }, - "course.assessment.generation.promptPlaceholder": { - "defaultMessage": "Type something here..." + "course.assessment.generation.generatePage": { + "defaultMessage": "Generate Programming Question" }, "course.assessment.generation.generateQuestion": { "defaultMessage": "Generate" }, - "course.assessment.generation.showInactive": { - "defaultMessage": "Show inactive items" - }, - "course.assessment.generation.mrq.numberOfQuestionsRange": { - "defaultMessage": "Please enter a number from {min} to {max}" + "course.assessment.generation.generateSuccess": { + "defaultMessage": "Generation for \"{title}\" successful." }, - "course.assessment.generation.enhanceMode": { - "defaultMessage": "Enhance" + "course.assessment.generation.languageField": { + "defaultMessage": "Language" }, - "course.assessment.generation.createMode": { - "defaultMessage": "Create New" + "course.assessment.generation.loadingSourceError": { + "defaultMessage": "Unable to load source question data." }, - "course.assessment.generation.enhanceModeTooltip": { - "defaultMessage": "Build upon your current question" + "course.assessment.generation.lockTooltip": { + "defaultMessage": "Lock to prevent changes to this section" }, - "course.assessment.generation.createModeTooltip": { - "defaultMessage": "Generate fresh questions from scratch" + "course.assessment.generation.mrq.exportAction": { + "defaultMessage": "Export" }, "course.assessment.generation.mrq.exportDialogHeader": { "defaultMessage": "Export Questions ({exportCount} selected)" }, - "course.assessment.generation.requireNonEmptyOptionError": { - "defaultMessage": "Question must have at least one non-empty option" + "course.assessment.generation.mrq.numberOfQuestionsField": { + "defaultMessage": "Number of Questions" }, - "course.assessment.generation.untitledQuestion": { - "defaultMessage": "Untitled Question" + "course.assessment.generation.mrq.numberOfQuestionsRange": { + "defaultMessage": "Please enter a number from {min} to {max}" }, - "course.assessment.question.multipleResponses.showOptions": { - "defaultMessage": "Show Options" + "course.assessment.generation.newTab": { + "defaultMessage": "New" }, - "course.assessment.question.multipleResponses.hideOptions": { - "defaultMessage": "Hide Options" + "course.assessment.generation.openExportDialog": { + "defaultMessage": "Export" }, - "course.assessment.question.multipleResponses.noOptions": { - "defaultMessage": "No options" + "course.assessment.generation.promptPlaceholder": { + "defaultMessage": "Type something here..." }, - "course.assessment.question.multipleResponses.title": { - "defaultMessage": "Title" + "course.assessment.generation.requireNonEmptyOptionError": { + "defaultMessage": "Question must have at least one non-empty option" }, - "course.assessment.generation.generateMrqPage": { - "defaultMessage": "Generate Multiple Response Question" + "course.assessment.generation.resetConversation": { + "defaultMessage": "Reset" }, - "course.assessment.generation.generateMcqPage": { - "defaultMessage": "Generate Multiple Choice Question" + "course.assessment.generation.showInactive": { + "defaultMessage": "Show inactive items" }, - "course.assessment.generation.generateMultipleSuccess": { - "defaultMessage": "Successfully generated {count} questions!" + "course.assessment.generation.sourceLanguageNotSupported": { + "defaultMessage": "Source question language not supported by the generation tool." }, - "course.assessment.generation.generateSuccess": { - "defaultMessage": "Generation for {title} successful." + "course.assessment.generation.unlockTooltip": { + "defaultMessage": "Unlock to continue editing this section" }, - "course.assessment.generation.generateError": { - "defaultMessage": "An error occurred generating question {title}." + "course.assessment.generation.untitledQuestion": { + "defaultMessage": "Untitled Question" }, - "course.assessment.generation.loadingSourceError": { - "defaultMessage": "Unable to load source question data." + "course.assessment.liveFeedback.comments": { + "defaultMessage": "Comments" }, - "course.assessment.generation.allFieldsLocked": { - "defaultMessage": "All fields are locked, so nothing can be generated." + "course.assessment.liveFeedback.lineHeader": { + "defaultMessage": "Line {lineNumber}" + }, + "course.assessment.liveFeedback.messageTimingTitle": { + "defaultMessage": "Generated at: {usedAt}" + }, + "course.assessment.liveFeedback.questionTitle": { + "defaultMessage": "Question {index}" + }, + "course.assessment.monitoring.accessGrantedForThisSessionOnly": { + "defaultMessage": "Access will be granted only for this browser session." }, "course.assessment.monitoring.alivePresenceHint": { "defaultMessage": "Last heartbeat was received in time." }, "course.assessment.monitoring.alivePresenceHintSUSMatches": { - "defaultMessage": "Last heartbeat was received in time and the SUS matches." + "defaultMessage": "Last heartbeat was received in time and came from an authorised browser, if browser authorisation is enabled." }, "course.assessment.monitoring.blankField": { "defaultMessage": "(blank)" }, + "course.assessment.monitoring.blocksAccessesFromInvalidSUS": { + "defaultMessage": "Block accesses from unauthorised browsers" + }, + "course.assessment.monitoring.blocksAccessesFromInvalidSUSHint": { + "defaultMessage": "If enabled, examinees using unauthorised browsers can't access this assessment. Instructors can override access with the session unlock password. Heartbeats from overridden browser sessions will always be valid (green) in the PulseGrid." + }, + "course.assessment.monitoring.browserAuthorizationMethod": { + "defaultMessage": "Browser authorisation method" + }, + "course.assessment.monitoring.browserAuthorizationMethodHint": { + "defaultMessage": "Choose how sessions are authorised as valid or invalid. Changes apply to all sessions and heartbeats immediately and updates live in PulseGrid." + }, "course.assessment.monitoring.cannotConnectToLiveMonitoringChannel": { "defaultMessage": "Oops, an error occurred when connecting to the live monitoring channel." }, "course.assessment.monitoring.connected": { "defaultMessage": "Connected" }, - "course.assessment.monitoring.connectedToLiveMonitoringChannel": { - "defaultMessage": "Connected to the live monitoring channel" + "course.assessment.monitoring.connecting": { + "defaultMessage": "Connecting" + }, + "course.assessment.monitoring.deltaFromPreviousHeartbeat": { + "defaultMessage": "{ms} ms from previous heartbeat" }, "course.assessment.monitoring.detailsOfNHeartbeats": { - "defaultMessage": "Details of the last {n} heartbeats" + "defaultMessage": "Last {n} heartbeats" }, "course.assessment.monitoring.disconnected": { "defaultMessage": "Disconnected" }, - "course.assessment.monitoring.disconnectedFromLiveMonitoringChannel": { - "defaultMessage": "Disconnected from the live monitoring channel" + "course.assessment.monitoring.enableBrowserAuthorization": { + "defaultMessage": "Authorise browsers that access this assessment" + }, + "course.assessment.monitoring.enableBrowserAuthorizationHint": { + "defaultMessage": "If enabled, PulseGrid will additionally check if an examinee is accessing this assessment from an authorised browser, based on the authorisation method you choose." + }, + "course.assessment.monitoring.examMonitoring": { + "defaultMessage": "Enable exam monitoring" + }, + "course.assessment.monitoring.examMonitoringHint": { + "defaultMessage": "If enabled, examinees' sessions will be monitored in real time from when they attempt the exam until they finalise it or the first 24 hours since their attempt, whichever is earlier. Instructors can monitor these sessions in PulseGrid." + }, + "course.assessment.monitoring.expiredSession": { + "defaultMessage": "Expired session. It has been at least 24 hours since the submission was made." }, "course.assessment.monitoring.filterByGroup": { "defaultMessage": "Filter by Group" }, + "course.assessment.monitoring.firstReceivedHeartbeat": { + "defaultMessage": "First received heartbeat" + }, "course.assessment.monitoring.generatedAt": { "defaultMessage": "Generated at" }, + "course.assessment.monitoring.intervalHint": { + "defaultMessage": "Controls how frequent heartbeats are sent from the examinees' browsers. Intervals are randomised between these two ranges." + }, + "course.assessment.monitoring.invalidBrowser": { + "defaultMessage": "Invalid browser configuration" + }, + "course.assessment.monitoring.invalidBrowserSubtitle": { + "defaultMessage": "Access to this assessment is not allowed with your current browser and/or its configuration. Contact your instructor for assistance." + }, + "course.assessment.monitoring.invalidHeartbeat": { + "defaultMessage": "Invalid" + }, "course.assessment.monitoring.ipAddress": { "defaultMessage": "IP Address" }, - "course.assessment.monitoring.lastHeartbeat": { - "defaultMessage": "Last heartbeat" - }, "course.assessment.monitoring.latePresenceHint": { "defaultMessage": "Next heartbeat hasn't been received in time, but still within the configured inter-heartbeats interval." }, "course.assessment.monitoring.live": { "defaultMessage": "Live" }, + "course.assessment.monitoring.liveHint": { + "defaultMessage": "This heartbeat was immediately received by the server." + }, + "course.assessment.monitoring.liveness": { + "defaultMessage": "Liveness" + }, + "course.assessment.monitoring.loadAllHeartbeats": { + "defaultMessage": "Load all" + }, + "course.assessment.monitoring.maxInterval": { + "defaultMessage": "Max interval" + }, + "course.assessment.monitoring.milliseconds": { + "defaultMessage": "ms" + }, + "course.assessment.monitoring.minInterval": { + "defaultMessage": "Min interval" + }, "course.assessment.monitoring.missingPresenceHint": { - "defaultMessage": "Next heartbeat hasn't been received in time." + "defaultMessage": "Next heartbeat hasn't been received in time, or the last heartbeat came from an unauthorised browser, if browser authorisation is enabled." + }, + "course.assessment.monitoring.needSUSAndSessionUnlockPassword": { + "defaultMessage": "You must enable browser authorisation and set a session unlock password to enable this." }, "course.assessment.monitoring.noActiveSessions": { - "defaultMessage": "No active sessions." + "defaultMessage": "No active sessions. No attempts have been made." + }, + "course.assessment.monitoring.offset": { + "defaultMessage": "Inter-heartbeat offset" + }, + "course.assessment.monitoring.offsetHint": { + "defaultMessage": "Controls how long PulseGrid should wait after the frequency interval before flagging a session as late." + }, + "course.assessment.monitoring.openSubmissionInNewTab": { + "defaultMessage": "Open submission in new tab" + }, + "course.assessment.monitoring.overrideAccess": { + "defaultMessage": "Override access" }, "course.assessment.monitoring.pulsegrid": { "defaultMessage": "PulseGrid" @@ -1662,9 +1970,42 @@ "course.assessment.monitoring.recentActivitiesHint": { "defaultMessage": "These logs will disappear if you close this tab!" }, + "course.assessment.monitoring.resetZoom": { + "defaultMessage": "Reset zoom" + }, + "course.assessment.monitoring.sebConfigKey": { + "defaultMessage": "Safe Exam Browser (SEB) Config Key" + }, + "course.assessment.monitoring.sebConfigKeyFieldHint": { + "defaultMessage": "Your SEB Config Key, not the Browser Exam Key, is generated from your specific SEB configuration. It stays the same across operating systems and SEB versions. Ensure this field is updated if you change your SEB configuration." + }, + "course.assessment.monitoring.sebConfigKeyFieldLabel": { + "defaultMessage": "SEB Config Key" + }, + "course.assessment.monitoring.sebConfigKeyHint": { + "defaultMessage": "Flags a session as valid if the examinee is using Safe Exam Browser (SEB) with a valid configuration. SEB generates a unique Config Key for a specific configuration. This method requires SEB 3.4 for Windows and SEB 3.0 for iOS and macOS, or later." + }, + "course.assessment.monitoring.sebPayload": { + "defaultMessage": "Safe Exam Browser (SEB) Config Key Hash & URL" + }, + "course.assessment.monitoring.secret": { + "defaultMessage": "Secret UA Substring (SUS)" + }, + "course.assessment.monitoring.secretHint": { + "defaultMessage": "If an examinee's browser's User Agent (UA) contains this case-sensitive secret, PulseGrid will flag that session as valid, and invalid otherwise. If you leave this blank, all sessions will be flagged as valid." + }, + "course.assessment.monitoring.sessionUnlockPassword": { + "defaultMessage": "Session unlock password" + }, "course.assessment.monitoring.stale": { "defaultMessage": "Stale" }, + "course.assessment.monitoring.staleHint": { + "defaultMessage": "This heartbeat wasn't immediately received by the server because the examinee's browser was temporarily unreachable. It was cached in the browser, and sent to the server when the browser was reachable again." + }, + "course.assessment.monitoring.stoppedSession": { + "defaultMessage": "Stopped session. Student may have finalised their submission." + }, "course.assessment.monitoring.summaryCorrectAsAt": { "defaultMessage": "Summary correct as at {time}" }, @@ -1672,7 +2013,10 @@ "defaultMessage": "Type" }, "course.assessment.monitoring.userAgent": { - "defaultMessage": "User Agent" + "defaultMessage": "User Agent (UA)" + }, + "course.assessment.monitoring.userAgentHint": { + "defaultMessage": "Flags a session as valid if the examinee's browser's User Agent (UA) contains a secret substring." }, "course.assessment.monitoring.userHeartbeatContinuedStreaming": { "defaultMessage": "{name}'s heartbeat just continued streaming." @@ -1680,9 +2024,66 @@ "course.assessment.monitoring.userHeartbeatNotReceivedInTime": { "defaultMessage": "{name}'s heartbeat wasn't received in time." }, + "course.assessment.monitoring.validHeartbeat": { + "defaultMessage": "Valid" + }, + "course.assessment.monitoring.zoomPanHint": { + "defaultMessage": "Pinch or scroll to zoom. Drag to pan." + }, "course.assessment.newAssessment": { "defaultMessage": "New Assessment" }, + "course.assessment.plagiarism.actions": { + "defaultMessage": "Actions" + }, + "course.assessment.plagiarism.baseSubmission": { + "defaultMessage": "Base Submission" + }, + "course.assessment.plagiarism.cannotManageSubmission": { + "defaultMessage": "You do not have permission to manage this submission." + }, + "course.assessment.plagiarism.comparedSubmission": { + "defaultMessage": "Compared Submission" + }, + "course.assessment.plagiarism.confirmStartMessage": { + "defaultMessage": "Running a new plagiarism check will remove the previous results." + }, + "course.assessment.plagiarism.confirmStartTitle": { + "defaultMessage": "Confirm Plagiarism Check?" + }, + "course.assessment.plagiarism.downloadPdf": { + "defaultMessage": "Download PDF" + }, + "course.assessment.plagiarism.lastRunTime": { + "defaultMessage": "Last run at: {date}" + }, + "course.assessment.plagiarism.notStarted": { + "defaultMessage": "No plagiarism check has been run" + }, + "course.assessment.plagiarism.plagiarism": { + "defaultMessage": "Plagiarism Results" + }, + "course.assessment.plagiarism.results": { + "defaultMessage": "Plagiarism Results (similarity between submissions)" + }, + "course.assessment.plagiarism.searchByStudentName": { + "defaultMessage": "Search by Student Name" + }, + "course.assessment.plagiarism.showSelfPlagiarism": { + "defaultMessage": "Include self-plagiarism comparisons (same student, different courses)" + }, + "course.assessment.plagiarism.similarityScore": { + "defaultMessage": "Similarity Score" + }, + "course.assessment.plagiarism.start": { + "defaultMessage": "New Plagiarism Check" + }, + "course.assessment.plagiarism.status": { + "defaultMessage": "Plagiarism Check Status" + }, + "course.assessment.plagiarism.viewReport": { + "defaultMessage": "View Report" + }, "course.assessment.question.forumPostResponses.enableTextResponse": { "defaultMessage": "Include a text field for students to provide further inputs" }, @@ -1755,6 +2156,9 @@ "course.assessment.question.multipleResponses.grading": { "defaultMessage": "Grading" }, + "course.assessment.question.multipleResponses.hideOptions": { + "defaultMessage": "Hide Options" + }, "course.assessment.question.multipleResponses.ignoresRandomization": { "defaultMessage": "Ignores randomization" }, @@ -1767,18 +2171,39 @@ "course.assessment.question.multipleResponses.maximumGrade": { "defaultMessage": "Maximum grade" }, + "course.assessment.question.multipleResponses.mustBeLessThanMaxAttachmentSize": { + "defaultMessage": "Must be at most {defaultMax}MB." + }, + "course.assessment.question.multipleResponses.mustBeLessThanMaxAttachments": { + "defaultMessage": "Must be at most {defaultMax}." + }, "course.assessment.question.multipleResponses.mustBeLessThanMaxMaximumGrade": { "defaultMessage": "Must be less than 1000." }, + "course.assessment.question.multipleResponses.mustHaveAtLeastOneResponse": { + "defaultMessage": "You must specify at least one response." + }, "course.assessment.question.multipleResponses.mustSpecifyAtLeastOneCorrectChoice": { "defaultMessage": "You must specify at least one correct choice." }, "course.assessment.question.multipleResponses.mustSpecifyChoice": { "defaultMessage": "You must specify a valid choice title." }, + "course.assessment.question.multipleResponses.mustSpecifyMaxAttachment": { + "defaultMessage": "You must specify a valid, positive maximum attachment number." + }, + "course.assessment.question.multipleResponses.mustSpecifyMaxAttachmentSize": { + "defaultMessage": "You must specify a valid, positive maximum attachment size." + }, "course.assessment.question.multipleResponses.mustSpecifyMaximumGrade": { "defaultMessage": "You must specify a valid, non-negative maximum grade to award." }, + "course.assessment.question.multipleResponses.mustSpecifyPositiveMaxAttachment": { + "defaultMessage": "Max Number of Attachments has to be at least 2." + }, + "course.assessment.question.multipleResponses.mustSpecifyPositiveMaxAttachmentSize": { + "defaultMessage": "Max Size has to be positive." + }, "course.assessment.question.multipleResponses.mustSpecifyPositiveMaximumGrade": { "defaultMessage": "Maximum grade has to be non-negative." }, @@ -1791,6 +2216,9 @@ "course.assessment.question.multipleResponses.newResponseCannotUndo": { "defaultMessage": "This is a new response. It will immediately disappear if you delete before saving it." }, + "course.assessment.question.multipleResponses.noOptions": { + "defaultMessage": "No options" + }, "course.assessment.question.multipleResponses.noSkillsCanCreateSkills": { "defaultMessage": "There are no skills in this course yet. You can create new skills at the Skills page." }, @@ -1827,6 +2255,9 @@ "course.assessment.question.multipleResponses.saveChangesFirstBeforeConvertingMcqMrq": { "defaultMessage": "Please save your changes before attempting to convert this question." }, + "course.assessment.question.multipleResponses.showOptions": { + "defaultMessage": "Show Options" + }, "course.assessment.question.multipleResponses.skills": { "defaultMessage": "Skills" }, @@ -1839,6 +2270,9 @@ "course.assessment.question.multipleResponses.staffOnlyCommentsHint": { "defaultMessage": "Useful for internal notes or documentations. Students will never see this." }, + "course.assessment.question.multipleResponses.title": { + "defaultMessage": "Title" + }, "course.assessment.question.multipleResponses.undoDeleteChoice": { "defaultMessage": "Undo delete choice" }, @@ -1893,6 +2327,9 @@ "course.assessment.question.programming.codaveriEvaluatorHint": { "defaultMessage": "On top of the default evaluation, this evaluator will provide automated code feedback powered by Codaveri when the submission is finalised. They will appear as draft comments for the instructors to review, edit, and publish." }, + "course.assessment.question.programming.codaveriEvaluatorNotSupported": { + "defaultMessage": "{languageName} is not supported by the Codaveri evaluator." + }, "course.assessment.question.programming.codeInserts": { "defaultMessage": "Code inserts" }, @@ -1914,15 +2351,18 @@ "course.assessment.question.programming.defaultEvaluator": { "defaultMessage": "Default" }, - "course.assessment.question.programming.defaultEvaluatorDependencyTitle": { - "defaultMessage": "{name}: Installed Dependencies" - }, "course.assessment.question.programming.defaultEvaluatorDependencyDescription": { "defaultMessage": "Submitted code is run in a containerized environment with the following dependencies installed locally.{br}If your programming question requires a dependency not listed below, contact us and we will consider adding it." }, + "course.assessment.question.programming.defaultEvaluatorDependencyTitle": { + "defaultMessage": "{name}: Installed Dependencies" + }, "course.assessment.question.programming.defaultEvaluatorHint": { "defaultMessage": "No fuss; just run the code according to the evaluation package below and report the test results." }, + "course.assessment.question.programming.defaultEvaluatorNotSupported": { + "defaultMessage": "{languageName} is not supported by the default evaluator." + }, "course.assessment.question.programming.dependencySearchText": { "defaultMessage": "Search dependencies by name" }, @@ -1954,7 +2394,7 @@ "defaultMessage": "Hold tight, evaluating all submissions with the new package..." }, "course.assessment.question.programming.evaluationLimits": { - "defaultMessage": "Evaluationlimits" + "defaultMessage": "Evaluation limits" }, "course.assessment.question.programming.evaluationTestCases": { "defaultMessage": "Evaluation test cases" @@ -1971,6 +2411,9 @@ "course.assessment.question.programming.expected": { "defaultMessage": "Expected" }, + "course.assessment.question.programming.expectedOutput": { + "defaultMessage": "Expected Output" + }, "course.assessment.question.programming.expression": { "defaultMessage": "Expression" }, @@ -2001,6 +2444,9 @@ "course.assessment.question.programming.inlineCode": { "defaultMessage": "Inline code" }, + "course.assessment.question.programming.input": { + "defaultMessage": "Input" + }, "course.assessment.question.programming.javaTestCasesHint": { "defaultMessage": "Expressions will be evaluated in the context of the submitted code. Their return values will be compared against the Expected expectations using the expectEquals(expression, expected) void. Its simplified definition is as follows, where Object has been overloaded for all Java primitives." }, @@ -2016,6 +2462,9 @@ "course.assessment.question.programming.languageAndEvaluation": { "defaultMessage": "Language and evaluation" }, + "course.assessment.question.programming.languageDeprecatedWarning": { + "defaultMessage": "Your selected language is deprecated. Please change it to another language." + }, "course.assessment.question.programming.lastUpdated": { "defaultMessage": "Last updated by {by} on {on}." }, @@ -2025,6 +2474,9 @@ "course.assessment.question.programming.liveFeedbackCustomPromptDescription": { "defaultMessage": "Add instructions to guide the generation of Get Help feedback here. If unsure, just leave this blank." }, + "course.assessment.question.programming.liveFeedbackNotSupported": { + "defaultMessage": "Get Help is not supported for {languageName}." + }, "course.assessment.question.programming.lowestGradingPriority": { "defaultMessage": "Lowest grading priority" }, @@ -2052,24 +2504,24 @@ "course.assessment.question.programming.packageCreationModeHint": { "defaultMessage": "You cannot change this mode once this question is successfully created. Choose wisely!" }, - "course.assessment.question.programming.packageImportSuccess": { - "defaultMessage": "The package was successfully imported." + "course.assessment.question.programming.packageImportEvaluationError": { + "defaultMessage": "An error occurred evaluating your solution against its test cases. Please double-check them and try again." + }, + "course.assessment.question.programming.packageImportEvaluationTimeout": { + "defaultMessage": "No response was received from an evaluator within the required time. This may indicate all our evaluators are busy right now, please try again later." + }, + "course.assessment.question.programming.packageImportGenericError": { + "defaultMessage": "The package could not be imported: {error}" }, "course.assessment.question.programming.packageImportInvalidPackage": { "defaultMessage": "The package could not be imported: the uploaded package does not have a valid structure." }, - "course.assessment.question.programming.packageImportEvaluationTimeout": { - "defaultMessage": "No response was received from an evaluator within the required time. This may indicate all our evaluators are busy right now, please try again later." + "course.assessment.question.programming.packageImportSuccess": { + "defaultMessage": "The package was successfully imported." }, "course.assessment.question.programming.packageImportTimeLimitExceeded": { "defaultMessage": "The solution did not finish evaluating the test cases in the specified time limit." }, - "course.assessment.question.programming.packageImportEvaluationError": { - "defaultMessage": "An error occurred evaluating your solution against its test cases. Please double-check them and try again." - }, - "course.assessment.question.programming.packageImportGenericError": { - "defaultMessage": "The package could not be imported: {error}" - }, "course.assessment.question.programming.packageInfoOnline": { "defaultMessage": "Generated evaluation package" }, @@ -2130,6 +2582,9 @@ "course.assessment.question.programming.standardError": { "defaultMessage": "Standard error" }, + "course.assessment.question.programming.standardInputOutputTestCasesHint": { + "defaultMessage": "Each test case launches a separate {language} console environment and provides input via standard input. The environment will combine the Prepend, student submission, and Append scripts into a single program and run it. The standard output of the program will be compared (as a string) to the expected output of the test case. We recommend handling input parsing and function calls in one of these scripts." + }, "course.assessment.question.programming.standardOutput": { "defaultMessage": "Standard output" }, @@ -2157,6 +2612,9 @@ "course.assessment.question.programming.timeLimit": { "defaultMessage": "Time limit" }, + "course.assessment.question.programming.timeLimitDetail": { + "defaultMessage": "{timeLimit, plural, one {# minute} other {# minutes}}" + }, "course.assessment.question.programming.uploadNewPackage": { "defaultMessage": "Upload a new package" }, @@ -2166,11 +2624,182 @@ "course.assessment.question.programming.uploadPackage": { "defaultMessage": "Manually create/edit offline and upload" }, - "course.assessment.question.programming.uploadPackageHint": { - "defaultMessage": "Pack the package as a ZIP file, then upload it here. Useful for complex test cases or if you host your course's evaluation packages in some version control system (e.g., Git, Mercurial, etc.)." + "course.assessment.question.programming.uploadPackageHint": { + "defaultMessage": "Pack the package as a ZIP file, then upload it here. Useful for complex test cases or if you host your course's evaluation packages in some version control system (e.g., Git, Mercurial, etc.)." + }, + "course.assessment.question.programminquestion.questionSavedRedirecting": { + "defaultMessage": "Question saved." + }, + "course.assessment.question.rubricBasedResponses.addNewCategory": { + "defaultMessage": "Add new category" + }, + "course.assessment.question.rubricBasedResponses.addNewLevel": { + "defaultMessage": "Add new grade" + }, + "course.assessment.question.rubricBasedResponses.aiGrading": { + "defaultMessage": "AI Grading" + }, + "course.assessment.question.rubricBasedResponses.aiGradingCustomPrompt": { + "defaultMessage": "Custom Prompt" + }, + "course.assessment.question.rubricBasedResponses.aiGradingCustomPromptDescription": { + "defaultMessage": "Add grading instructions (e.g. question context, model answer, feedback tone). Leave blank if unsure." + }, + "course.assessment.question.rubricBasedResponses.aiGradingModelAnswer": { + "defaultMessage": "Model Answer" + }, + "course.assessment.question.rubricBasedResponses.aiGradingModelAnswerDescription": { + "defaultMessage": "Add an example answer that would get the maximum grades in each rubric category. Leave blank if unsure." + }, + "course.assessment.question.rubricBasedResponses.bonusReservedNames": { + "defaultMessage": "After finalization, a special category named 'Moderation' will be added automatically. It allows graders to award bonus or penalty points at their discretion." + }, + "course.assessment.question.rubricBasedResponses.categoryGrade": { + "defaultMessage": "Grade" + }, + "course.assessment.question.rubricBasedResponses.categoryGradeExplanation": { + "defaultMessage": "Explanation" + }, + "course.assessment.question.rubricBasedResponses.categoryMaximumGrade": { + "defaultMessage": "Max" + }, + "course.assessment.question.rubricBasedResponses.categoryName": { + "defaultMessage": "Category Name" + }, + "course.assessment.question.rubricBasedResponses.enableAiGrading": { + "defaultMessage": "Enable AI to auto-grade submissions" + }, + "course.assessment.question.rubricBasedResponses.enableAiGradingDescription": { + "defaultMessage": "AI will assign rubric scores and draft feedback for you to review and publish." + }, + "course.assessment.question.rubricBasedResponses.rubric": { + "defaultMessage": "Rubric" + }, + "course.assessment.question.rubricBasedResponses.rubricHint": { + "defaultMessage": "Rubric is used to grade the student's submission." + }, + "course.assessment.question.rubricPlayground.addAnswersPromptAction": { + "defaultMessage": "Add" + }, + "course.assessment.question.rubricPlayground.addAnswersTitle": { + "defaultMessage": "Add Sample Answers" + }, + "course.assessment.question.rubricPlayground.addExistingAnswers": { + "defaultMessage": "Add existing answers" + }, + "course.assessment.question.rubricPlayground.addRandomStudentAnswers": { + "defaultMessage": "Add {inputComponent} random student answer(s)" + }, + "course.assessment.question.rubricPlayground.addSampleAnswers": { + "defaultMessage": "Add Sample Answers" + }, + "course.assessment.question.rubricPlayground.answer": { + "defaultMessage": "Answer" + }, + "course.assessment.question.rubricPlayground.apply": { + "defaultMessage": "Apply" + }, + "course.assessment.question.rubricPlayground.applyFailure": { + "defaultMessage": "Failed to apply grading results" + }, + "course.assessment.question.rubricPlayground.applySuccess": { + "defaultMessage": "Grading rubric, prompt, and results successfully applied." + }, + "course.assessment.question.rubricPlayground.applyWillGradeAllAnswers": { + "defaultMessage": "Applying this rubric will assign grades to all student answers, including the ones not yet evaluated on this page." + }, + "course.assessment.question.rubricPlayground.applyingRubricGradingData": { + "defaultMessage": "Applying rubric grading data..." + }, + "course.assessment.question.rubricPlayground.categoryHeading": { + "defaultMessage": "C{index}" + }, + "course.assessment.question.rubricPlayground.compare": { + "defaultMessage": "Compare" + }, + "course.assessment.question.rubricPlayground.comparingRevisions": { + "defaultMessage": "Comparing {count} revisions" + }, + "course.assessment.question.rubricPlayground.confirmAIGradingApplication": { + "defaultMessage": "Confirm AI Grading Application" + }, + "course.assessment.question.rubricPlayground.confirmProceed": { + "defaultMessage": "Are you sure you wish to proceed?" + }, + "course.assessment.question.rubricPlayground.dismiss": { + "defaultMessage": "Dismiss" + }, + "course.assessment.question.rubricPlayground.evaluate": { + "defaultMessage": "Evaluate" + }, + "course.assessment.question.rubricPlayground.evaluateAll": { + "defaultMessage": "Evaluate All ({count})" + }, + "course.assessment.question.rubricPlayground.evaluateRemaining": { + "defaultMessage": "Evaluate Remaining ({count})" + }, + "course.assessment.question.rubricPlayground.evaluating": { + "defaultMessage": "Evaluating" + }, + "course.assessment.question.rubricPlayground.feedback": { + "defaultMessage": "Feedback" + }, + "course.assessment.question.rubricPlayground.gradingCategories": { + "defaultMessage": "Grading Categories" + }, + "course.assessment.question.rubricPlayground.gradingPrompt": { + "defaultMessage": "Grading Prompt" + }, + "course.assessment.question.rubricPlayground.gradingPromptDescription": { + "defaultMessage": "Instructions to guide the AI in grading and giving feedback." + }, + "course.assessment.question.rubricPlayground.modelAnswer": { + "defaultMessage": "Model Answer" + }, + "course.assessment.question.rubricPlayground.modelAnswerDescription": { + "defaultMessage": "An example that scores the maximum for each category." + }, + "course.assessment.question.rubricPlayground.noAnswers": { + "defaultMessage": "No sample answers have been added. Add some to get started." + }, + "course.assessment.question.rubricPlayground.notLatestRevisionWarning": { + "defaultMessage": "You have selected to apply a rubric which is not the latest revision saved on this page." + }, + "course.assessment.question.rubricPlayground.questionGrade": { + "defaultMessage": "Grade" + }, + "course.assessment.question.rubricPlayground.reevaluate": { + "defaultMessage": "Re-evaluate" + }, + "course.assessment.question.rubricPlayground.reevaluateAll": { + "defaultMessage": "Re-evaluate All ({count})" + }, + "course.assessment.question.rubricPlayground.rubricPlayground": { + "defaultMessage": "Rubric Playground" + }, + "course.assessment.question.rubricPlayground.sampleAnswerEvaluations": { + "defaultMessage": "Sample Answer Evaluations" + }, + "course.assessment.question.rubricPlayground.savedRubric": { + "defaultMessage": "Saved Rubric, {date}" + }, + "course.assessment.question.rubricPlayground.searchAnswersPlaceholder": { + "defaultMessage": "Search answers by student name or grade" + }, + "course.assessment.question.rubricPlayground.student": { + "defaultMessage": "Student" + }, + "course.assessment.question.rubricPlayground.totalGrade": { + "defaultMessage": "Total" + }, + "course.assessment.question.rubricPlayground.viewEditRubric": { + "defaultMessage": "View / Edit Rubric" + }, + "course.assessment.question.rubricPlayground.writeAnswerPlaceholder": { + "defaultMessage": "Write the answer here" }, - "course.assessment.question.programminquestion.questionSavedRedirecting": { - "defaultMessage": "Question saved." + "course.assessment.question.rubricPlayground.writeCustomAnswer": { + "defaultMessage": "Write a custom answer" }, "course.assessment.question.scribing.ScribingQuestionForm.cannotBeBlankValidationError": { "defaultMessage": "Cannot be blank." @@ -2241,8 +2870,14 @@ "course.assessment.question.textResponses.addSolution": { "defaultMessage": "Add a new solution" }, - "course.assessment.question.textResponses.allowFileUpload": { - "defaultMessage": "Allow file upload in the answer" + "course.assessment.question.textResponses.attachmentSettingRequired": { + "defaultMessage": "Attachment Setting should be defined in this question" + }, + "course.assessment.question.textResponses.attachmentSettings": { + "defaultMessage": "Attachment Settings" + }, + "course.assessment.question.textResponses.attachmentSettingsDescription": { + "defaultMessage": "When students are attempting this question," }, "course.assessment.question.textResponses.deleteSolution": { "defaultMessage": "Delete solution" @@ -2256,9 +2891,24 @@ "course.assessment.question.textResponses.grade": { "defaultMessage": "Grade" }, + "course.assessment.question.textResponses.isAttachmentRequired": { + "defaultMessage": "Require file upload for this question" + }, "course.assessment.question.textResponses.keyword": { "defaultMessage": "Keyword" }, + "course.assessment.question.textResponses.maxAttachmentSize": { + "defaultMessage": "Max Size per Attachment" + }, + "course.assessment.question.textResponses.maxAttachments": { + "defaultMessage": "Max Number of Attachments" + }, + "course.assessment.question.textResponses.multipleAttachments": { + "defaultMessage": "Multiple Attachments" + }, + "course.assessment.question.textResponses.multipleFileAttachmentDescription": { + "defaultMessage": "They can upload several attachments." + }, "course.assessment.question.textResponses.mustSpecifyGrade": { "defaultMessage": "You must specify a valid number for grade." }, @@ -2271,6 +2921,18 @@ "course.assessment.question.textResponses.newSolutionCannotUndo": { "defaultMessage": "This is a new solution. It will immediately disappear if you delete before saving it." }, + "course.assessment.question.textResponses.noAttachment": { + "defaultMessage": "No Attachment" + }, + "course.assessment.question.textResponses.noAttachmentDescription": { + "defaultMessage": "They will not be able to upload any attachment." + }, + "course.assessment.question.textResponses.singleFileAttachment": { + "defaultMessage": "Single Attachment" + }, + "course.assessment.question.textResponses.singleFileAttachmentDescription": { + "defaultMessage": "They can only upload one attachment." + }, "course.assessment.question.textResponses.solution": { "defaultMessage": "Solution" }, @@ -2289,155 +2951,23 @@ "course.assessment.question.textResponses.solutionsHint": { "defaultMessage": "Adding solutions allows the answer to be autograded. Students can only input plain text." }, - "course.assessment.question.textResponses.textResponseNote": { - "defaultMessage": "Note: If no solutions are provided, the autograder will always award the maximum grade." - }, - "course.assessment.question.textResponses.undoDeleteSolution": { - "defaultMessage": "Undo delete solution" - }, - "course.assessment.question.textResponses.zeroGrade": { - "defaultMessage": "0.0" - }, "course.assessment.question.textResponses.templateText": { "defaultMessage": "Template" }, "course.assessment.question.textResponses.templateTextDescription": { "defaultMessage": "Text that appears in the answer area when students attempt this question for the first time." }, - "course.assessment.question.rubricPlayground.rubricPlayground": { - "defaultMessage": "Rubric Playground" - }, - "course.assessment.question.rubricPlayground.savedRubric": { - "defaultMessage": "Saved Rubric, {date}" - }, - "course.assessment.question.rubricPlayground.viewEditRubric": { - "defaultMessage": "View / Edit Rubric" - }, - "course.assessment.question.rubricPlayground.evaluate": { - "defaultMessage": "Evaluate" - }, - "course.assessment.question.rubricPlayground.compare": { - "defaultMessage": "Compare" - }, - "course.assessment.question.rubricPlayground.apply": { - "defaultMessage": "Apply" - }, - "course.assessment.question.rubricPlayground.confirmAIGradingApplication": { - "defaultMessage": "Confirm AI Grading Application" - }, - "course.assessment.question.rubricPlayground.applyingRubricGradingData": { - "defaultMessage": "Applying rubric grading data..." - }, - "course.assessment.question.rubricPlayground.applySuccess": { - "defaultMessage": "Grading rubric, prompt, and results successfully applied." - }, - "course.assessment.question.rubricPlayground.applyFailure": { - "defaultMessage": "Failed to apply grading results" - }, - "course.assessment.question.rubricPlayground.notLatestRevisionWarning": { - "defaultMessage": "You have selected to apply a rubric which is not the latest revision saved on this page." - }, - "course.assessment.question.rubricPlayground.applyWillGradeAllAnswers": { - "defaultMessage": "Applying this rubric will assign grades to all student answers, including the ones not yet evaluated on this page." - }, - "course.assessment.question.rubricPlayground.confirmProceed": { - "defaultMessage": "Are you sure you wish to proceed?" - }, - "course.assessment.question.rubricPlayground.sampleAnswerEvaluations": { - "defaultMessage": "Sample Answer Evaluations" - }, - "course.assessment.question.rubricPlayground.addSampleAnswers": { - "defaultMessage": "Add Sample Answers" - }, - "course.assessment.question.rubricPlayground.evaluateAll": { - "defaultMessage": "Evaluate All ({count})" - }, - "course.assessment.question.rubricPlayground.reevaluateAll": { - "defaultMessage": "Re-evaluate All ({count})" - }, - "course.assessment.question.rubricPlayground.evaluateRemaining": { - "defaultMessage": "Evaluate Remaining ({count})" - }, - "course.assessment.question.rubricPlayground.comparingRevisions": { - "defaultMessage": "Comparing {count} revisions" - }, - "course.assessment.question.rubricPlayground.addSampleAnswersTitle": { - "defaultMessage": "Add Sample Answers" - }, - "course.assessment.question.rubricPlayground.add": { - "defaultMessage": "Add" - }, - "course.assessment.question.rubricPlayground.addExistingAnswers": { - "defaultMessage": "Add existing answers" - }, - "course.assessment.question.rubricPlayground.student": { - "defaultMessage": "Student" - }, - "course.assessment.question.rubricPlayground.questionGrade": { - "defaultMessage": "Grade" - }, - "course.assessment.question.rubricPlayground.categoryHeading": { - "defaultMessage": "C{index}" - }, - "course.assessment.question.rubricPlayground.answer": { - "defaultMessage": "Answer" - }, - "course.assessment.question.rubricPlayground.searchAnswersPlaceholder": { - "defaultMessage": "Search answers by student name or grade" - }, - "course.assessment.question.rubricPlayground.addRandomStudentAnswers": { - "defaultMessage": "Add {inputComponent} random student answer(s)" - }, - "course.assessment.question.rubricPlayground.writeCustomAnswer": { - "defaultMessage": "Write a custom answer" - }, - "course.assessment.question.rubricPlayground.writeAnswerPlaceholder": { - "defaultMessage": "Write the answer here" - }, - "course.assessment.question.rubricPlayground.dismiss": { - "defaultMessage": "Dismiss" - }, - "course.assessment.question.rubricPlayground.noAnswers": { - "defaultMessage": "No sample answers have been added. Add some to get started." - }, - "course.assessment.question.rubricPlayground.reevaluate": { - "defaultMessage": "Re-evaluate" - }, - "course.assessment.question.rubricPlayground.totalGrade": { - "defaultMessage": "Total" - }, - "course.assessment.question.rubricPlayground.feedback": { - "defaultMessage": "Feedback" - }, - "course.assessment.question.rubricPlayground.evaluating": { - "defaultMessage": "Evaluating" - }, - "course.assessment.question.rubricPlayground.gradingPrompt": { - "defaultMessage": "Grading Prompt" - }, - "course.assessment.question.rubricPlayground.gradingPromptDescription": { - "defaultMessage": "Instructions to guide the AI in grading and giving feedback." - }, - "course.assessment.question.rubricPlayground.modelAnswer": { - "defaultMessage": "Model Answer" - }, - "course.assessment.question.rubricPlayground.modelAnswerDescription": { - "defaultMessage": "An example that scores the maximum for each category." - }, - "course.assessment.question.rubricPlayground.gradingCategories": { - "defaultMessage": "Grading Categories" - }, - "course.assessment.question.rubricPlayground.addNewCategory": { - "defaultMessage": "Add New Category" + "course.assessment.question.textResponses.textResponseNote": { + "defaultMessage": "Note: If no solutions are provided, the autograder will always award the maximum grade." }, - "course.assessment.question.rubricPlayground.categoryName": { - "defaultMessage": "Category Name" + "course.assessment.question.textResponses.undoDeleteSolution": { + "defaultMessage": "Undo delete solution" }, - "course.assessment.question.rubricPlayground.max": { - "defaultMessage": "Max" + "course.assessment.question.textResponses.validAttachmentSettingValues": { + "defaultMessage": "Attachment Settings should be either no attachment, single file attachment, or multiple file attachment" }, - "course.assessment.question.rubricPlayground.addNewGrade": { - "defaultMessage": "Add New Grade" + "course.assessment.question.textResponses.zeroGrade": { + "defaultMessage": "0.0" }, "course.assessment.session.assessmentNotStarted": { "defaultMessage": "The assessment has not started yet. Please come back after {startDate}." @@ -2463,9 +2993,6 @@ "course.assessment.show.assessmentOnlyAvailableFrom": { "defaultMessage": "This assessment will only be available from" }, - "course.assessment.show.audioResponse": { - "defaultMessage": "Audio Response" - }, "course.assessment.show.baseExp": { "defaultMessage": "Base EXP" }, @@ -2499,6 +3026,9 @@ "course.assessment.show.chooseAssessmentToDuplicateInto": { "defaultMessage": "Choose an assessment to duplicate into" }, + "course.assessment.show.comprehension": { + "defaultMessage": "Comprehension" + }, "course.assessment.show.delete": { "defaultMessage": "Delete" }, @@ -2556,9 +3086,15 @@ "course.assessment.show.errorMovingQuestion": { "defaultMessage": "An error occurred while moving the question." }, + "course.assessment.show.failedSyncingWithKoditsu": { + "defaultMessage": "Not Synced with Koditsu" + }, "course.assessment.show.fileUpload": { "defaultMessage": "File Upload" }, + "course.assessment.show.fileUploadDescription": { + "defaultMessage": "Settings for the number of attachments allowed (none, one, or multiple)" + }, "course.assessment.show.files": { "defaultMessage": "Files" }, @@ -2571,20 +3107,20 @@ "course.assessment.show.forumPostResponse": { "defaultMessage": "Forum Post Response" }, - "course.assessment.show.gradedTestCases": { - "defaultMessage": "Graded test cases" - }, "course.assessment.show.generate": { "defaultMessage": "Generate Questions" }, - "course.assessment.show.generateTooltip": { - "defaultMessage": "Collaborate with Codaveri AI to create questions" + "course.assessment.show.generateFromProgrammingQuestion": { + "defaultMessage": "Generate a similar question with Codaveri AI" }, "course.assessment.show.generateFromQuestion": { "defaultMessage": "Generate a similar question with AI" }, - "course.assessment.show.generateFromProgrammingQuestion": { - "defaultMessage": "Generate a similar question with Codaveri AI" + "course.assessment.show.generateTooltip": { + "defaultMessage": "Collaborate with Codaveri AI to create questions" + }, + "course.assessment.show.gradedTestCases": { + "defaultMessage": "Graded test cases" }, "course.assessment.show.gradingMode": { "defaultMessage": "Grading mode" @@ -2601,6 +3137,9 @@ "course.assessment.show.hideOptions": { "defaultMessage": "Hide options" }, + "course.assessment.show.koditsuMode": { + "defaultMessage": "Koditsu" + }, "course.assessment.show.manageComponents": { "defaultMessage": "Manage Components in Course Settings" }, @@ -2649,6 +3188,9 @@ "course.assessment.show.newQuestion": { "defaultMessage": "New Question" }, + "course.assessment.show.newRubricBasedResponse": { + "defaultMessage": "New Rubric Based Response Question" + }, "course.assessment.show.newScribing": { "defaultMessage": "New Scribing Question" }, @@ -2706,6 +3248,9 @@ "course.assessment.show.requirementsHint": { "defaultMessage": "The following items must be fulfilled to unlock this assessment." }, + "course.assessment.show.rubricBasedResponse": { + "defaultMessage": "Rubric-Based Response" + }, "course.assessment.show.scribing": { "defaultMessage": "Scribing" }, @@ -2715,15 +3260,15 @@ "course.assessment.show.showMcqMrqSolution": { "defaultMessage": "Show MCQ/MRQ solutions" }, - "course.assessment.show.showRubricToStudents": { - "defaultMessage": "Show rubric breakdown to students" - }, "course.assessment.show.showMcqSubmitResult": { "defaultMessage": "Show MCQ submit result" }, "course.assessment.show.showOptions": { "defaultMessage": "Show options" }, + "course.assessment.show.showRubricToStudents": { + "defaultMessage": "Show rubric breakdown to students" + }, "course.assessment.show.sureChangingQuestionType": { "defaultMessage": "Sure you're changing this question type?" }, @@ -2733,6 +3278,12 @@ "course.assessment.show.sureDeletingQuestion": { "defaultMessage": "Sure you're deleting this question?" }, + "course.assessment.show.syncedWithKoditsu": { + "defaultMessage": "Synced with Koditsu" + }, + "course.assessment.show.syncingWithKoditsu": { + "defaultMessage": "Syncing with Koditsu" + }, "course.assessment.show.textResponse": { "defaultMessage": "Text Response" }, @@ -2748,6 +3299,9 @@ "course.assessment.show.unsubmittingAndChangingQuestionType": { "defaultMessage": "Unsubmitting submissions and changing your question type..." }, + "course.assessment.show.voiceResponse": { + "defaultMessage": "Audio Response" + }, "course.assessment.show.whileHoldingToCancelMoving": { "defaultMessage": "while holding to cancel moving." }, @@ -2850,93 +3404,30 @@ "course.assessment.skills.SkillsTable.uncategorised": { "defaultMessage": "Uncategorised Skills" }, - "course.assessment.liveFeedback.questionTitle": { - "defaultMessage": "Question {index}" - }, - "course.assessment.liveFeedback.messageTimingTitle": { - "defaultMessage": "Generated at: {usedAt}" - }, - "course.assessment.liveFeedback.liveFeedbackName": { - "defaultMessage": "Get Help" - }, - "course.assessment.liveFeedback.comments": { - "defaultMessage": "Comments" - }, - "course.assessment.liveFeedback.lineHeader": { - "defaultMessage": "Line {lineNumber}" - }, - "course.assessment.submission.GetHelpChatPage.chatInputText": { - "defaultMessage": "How can we help you?" - }, - "course.assessment.submission.GetHelpChatPage.chatMessagesRemaining": { - "defaultMessage": "{numMessages} / {maxMessages} {numMessages, plural, one {message} other {messages}} remaining" - }, - "course.assessment.submission.GetHelpChatPage.noChatMessagesRemaining": { - "defaultMessage": "You have reached the message limit for this question." - }, - "course.assessment.submission.GetHelpChatPage.codeUpdated": { - "defaultMessage": "Code Updated" - }, - "course.assessment.submission.GetHelpChatPage.ConversationArea.lineNumber": { - "defaultMessage": "Line {lineNumber}" - }, - "course.assessment.submission.GetHelpChatPage.ConversationArea.fileNameAndLineNumber": { - "defaultMessage": "{filename}:{lineNumber}" - }, - "course.assessment.submission.GetHelpChatPage.ConversationArea.threadExpired": { - "defaultMessage": "The chat above has ended. Start a new chat?" - }, - "course.assessment.plagiarism.plagiarism": { - "defaultMessage": "Plagiarism Results" - }, - "course.assessment.plagiarism.status": { - "defaultMessage": "Plagiarism Check Status" - }, - "course.assessment.plagiarism.lastRunTime": { - "defaultMessage": "Last run at: {date}" - }, - "course.assessment.plagiarism.start": { - "defaultMessage": "New Plagiarism Check" - }, - "course.assessment.plagiarism.notStarted": { - "defaultMessage": "No plagiarism check has been run" - }, - "course.assessment.plagiarism.confirmStartTitle": { - "defaultMessage": "Confirm Plagiarism Check?" - }, - "course.assessment.plagiarism.confirmStartMessage": { - "defaultMessage": "Running a new plagiarism check will remove the previous results." - }, - "course.assessment.plagiarism.results": { - "defaultMessage": "Plagiarism Results (similarity between submissions)" - }, - "course.assessment.plagiarism.baseSubmission": { - "defaultMessage": "Base Submission" - }, - "course.assessment.plagiarism.comparedSubmission": { - "defaultMessage": "Compared Submission" - }, - "course.assessment.plagiarism.similarityScore": { - "defaultMessage": "Similarity Score" + "course.assessment.statistics.ancestorFail": { + "defaultMessage": "Failed to fetch past iterations of this assessment." }, - "course.assessment.plagiarism.actions": { - "defaultMessage": "Actions" + "course.assessment.statistics.ancestorSelect.current": { + "defaultMessage": "Current" }, - "course.assessment.plagiarism.viewReport": { - "defaultMessage": "View Report" + "course.assessment.statistics.ancestorSelect.fromCourse": { + "defaultMessage": "From {courseTitle}" }, - "course.assessment.plagiarism.downloadPdf": { - "defaultMessage": "Download PDF" + "course.assessment.statistics.ancestorSelect.subtitle": { + "defaultMessage": "Compare against past versions of this assessment:" }, - "course.assessment.plagiarism.searchByStudentName": { - "defaultMessage": "Search by Student Name" + "course.assessment.statistics.ancestorSelect.title": { + "defaultMessage": "Duplication History" }, - "course.assessment.plagiarism.showSelfPlagiarism": { - "defaultMessage": "Include self-plagiarism comparisons (same student, different courses)" + "course.assessment.statistics.ancestorStatisticsFail": { + "defaultMessage": "Failed to fetch ancestor's statistics." }, "course.assessment.statistics.answers": { "defaultMessage": "Answers" }, + "course.assessment.statistics.attemptCount": { + "defaultMessage": "Attempt Count" + }, "course.assessment.statistics.attempts.filename": { "defaultMessage": "Question-level Attempt Statistics for {assessment}" }, @@ -2949,21 +3440,78 @@ "course.assessment.statistics.closePrompt": { "defaultMessage": "Close" }, + "course.assessment.statistics.comments": { + "defaultMessage": "Comments" + }, + "course.assessment.statistics.duplicationHistory": { + "defaultMessage": "Duplication History" + }, + "course.assessment.statistics.email": { + "defaultMessage": "Email" + }, + "course.assessment.statistics.fail": { + "defaultMessage": "Failed to fetch statistics." + }, + "course.assessment.statistics.gradeDisplay": { + "defaultMessage": "Grade: {grade} / {maxGrade}" + }, + "course.assessment.statistics.gradeDistribution": { + "defaultMessage": "Grade Distribution" + }, + "course.assessment.statistics.gradeDistribution.datasetLabel": { + "defaultMessage": "Distribution" + }, + "course.assessment.statistics.gradeDistribution.xAxisLabel": { + "defaultMessage": "Grades" + }, + "course.assessment.statistics.gradeDistribution.yAxisLabel": { + "defaultMessage": "Submissions" + }, "course.assessment.statistics.grader": { "defaultMessage": "Grader" }, + "course.assessment.statistics.gradesPerQuestion": { + "defaultMessage": "Grades Per Question" + }, "course.assessment.statistics.grayCellLegend": { "defaultMessage": "Undecided (question is Non-autogradable)" }, "course.assessment.statistics.group": { "defaultMessage": "Group" }, - "course.assessment.statistics.legendHigherusage": { - "defaultMessage": "Higher Usage" + "course.assessment.statistics.header": { + "defaultMessage": "Statistics for {title}" + }, + "course.assessment.statistics.includePhantom": { + "defaultMessage": "Include Phantom Student" }, - "course.assessment.statistics.legendLowerUsage": { + "course.assessment.statistics.legendHigherLabelGrade": { + "defaultMessage": "Higher Grade" + }, + "course.assessment.statistics.legendHigherLabelGradeDiff": { + "defaultMessage": "More Improvement" + }, + "course.assessment.statistics.legendLowerLabelGrade": { + "defaultMessage": "Lower Grade" + }, + "course.assessment.statistics.legendLowerLabelGradeDiff": { + "defaultMessage": "Less Improvement" + }, + "course.assessment.statistics.legendLowerLabelMessagesSent": { "defaultMessage": "Lower Usage" }, + "course.assessment.statistics.legendLowerLabelWordCount": { + "defaultMessage": "Lower Word Count" + }, + "course.assessment.statistics.legendUpperLabelMessagesSent": { + "defaultMessage": "Higher Usage" + }, + "course.assessment.statistics.legendUpperLabelWordCount": { + "defaultMessage": "Higher Word Count" + }, + "course.assessment.statistics.liveFeedback": { + "defaultMessage": "Get Help" + }, "course.assessment.statistics.liveFeedback.filename": { "defaultMessage": "Question-level Get Help Statistics for {assessment}" }, @@ -2988,78 +3536,30 @@ "course.assessment.statistics.nameGroupsSearchText": { "defaultMessage": "Search by Name or Groups" }, + "course.assessment.statistics.noIncludePhantom": { + "defaultMessage": "*All statistics in this duplicated assessments does not include Phantom Students" + }, "course.assessment.statistics.noSubmission": { "defaultMessage": "No submission yet" }, "course.assessment.statistics.onlyForAutogradableAssessment": { "defaultMessage": "This table is only displayed for Assessment with at least one Autograded Questions" }, + "course.assessment.statistics.pastAnswerTitle": { + "defaultMessage": "Submitted At: {submittedAt}" + }, "course.assessment.statistics.questionDisplayTitle": { "defaultMessage": "Q{index} for {student}" }, "course.assessment.statistics.questionIndex": { "defaultMessage": "Q{index}" }, - "course.assessment.statistics.total": { - "defaultMessage": "Total" - }, - "course.assessment.statistics.workflowState": { - "defaultMessage": "Status" - }, - "course.assessment.statistics.ancestorFail": { - "defaultMessage": "Failed to fetch past iterations of this assessment." - }, - "course.assessment.statistics.ancestorStatisticsFail": { - "defaultMessage": "Failed to fetch ancestor's statistics." - }, - "course.assessment.statistics.fail": { - "defaultMessage": "Failed to fetch statistics." - }, - "course.assessment.statistics.gradeDistribution": { - "defaultMessage": "Grade Distribution" - }, - "course.assessment.statistics.gradeViolin.datasetLabel": { - "defaultMessage": "Distribution" - }, - "course.assessment.statistics.gradeViolin.xAxisLabel": { - "defaultMessage": "Grades" - }, - "course.assessment.statistics.gradeViolin.yAxisLabel": { - "defaultMessage": "Submissions" - }, - "course.assessment.statistics.ancestorSelect.current": { - "defaultMessage": "Current" - }, - "course.assessment.statistics.ancestorSelect.fromCourse": { - "defaultMessage": "From {courseTitle}" - }, - "course.assessment.statistics.ancestorSelect.subtitle": { - "defaultMessage": "Compare against past versions of this assessment:" - }, - "course.assessment.statistics.ancestorSelect.title": { - "defaultMessage": "Duplication History" - }, - "course.assessment.statistics.attemptCount": { - "defaultMessage": "Attempt Count" - }, - "course.assessment.statistics.duplicationHistory": { - "defaultMessage": "Duplication History" - }, - "course.assessment.statistics.gradesPerQuestion": { - "defaultMessage": "Grades Per Question" - }, - "course.assessment.statistics.includePhantom": { - "defaultMessage": "Include Phantom Student" - }, - "course.assessment.statistics.liveFeedback": { - "defaultMessage": "Get Help" - }, - "course.assessment.statistics.header": { - "defaultMessage": "Statistics for {title}" - }, "course.assessment.statistics.statistics": { "defaultMessage": "Statistics" }, + "course.assessment.statistics.submissionPage": { + "defaultMessage": "Go to Answer Page" + }, "course.assessment.statistics.submissionStatuses": { "defaultMessage": "Submission Statuses" }, @@ -3078,9 +3578,18 @@ "course.assessment.statistics.submissionTimeGradeChart.xAxisLabel.withoutDeadline": { "defaultMessage": "Submission Date" }, + "course.assessment.statistics.total": { + "defaultMessage": "Total" + }, + "course.assessment.statistics.workflowState": { + "defaultMessage": "Status" + }, "course.assessment.submission.Annotations.comment": { "defaultMessage": "Add Comment" }, + "course.assessment.submission.Answer.rendererNotImplemented": { + "defaultMessage": "The display for this question type has not been implemented yet." + }, "course.assessment.submission.CodaveriFeedbackStatus.codaveriFeedbackStatus": { "defaultMessage": "Codaveri Feedback Status" }, @@ -3099,14 +3608,77 @@ "course.assessment.submission.EvaluatorErrorPanel.emailSubject": { "defaultMessage": "[Bug Report] Evaluator Error" }, + "course.assessment.submission.FileInput.exactlyOneFileUploadAllowed": { + "defaultMessage": "*You must upload EXACTLY 1 file for this question" + }, + "course.assessment.submission.FileInput.fileName": { + "defaultMessage": "{index}. {name}" + }, + "course.assessment.submission.FileInput.fileTooLargeErrorMessage": { + "defaultMessage": "The following files have size larger than allowed ({maxAttachmentSize} MB)" + }, + "course.assessment.submission.FileInput.fileUploadErrorTitle": { + "defaultMessage": "Error in Uploading Files" + }, + "course.assessment.submission.FileInput.onlyOneFileUploadAllowed": { + "defaultMessage": "*You can only upload AT MOST {maxAttachments} file for this question" + }, + "course.assessment.submission.FileInput.requiredUploadLimitedNumberOfFiles": { + "defaultMessage": "*You can upload AT LEAST 1 and AT MOST {maxAttachments} files for this question" + }, + "course.assessment.submission.FileInput.tooManyFilesErrorMessage": { + "defaultMessage": "You have attempted to upload {numFiles} files, but ONLY {maxAttachmentsAllowed} {maxAttachmentsAllowed, plural, one {file} other {files}} can be uploaded {numAttachments, plural, =0 {} one {since 1 file has been uploaded before} other {since {numAttachments} files has been uploaded before}}" + }, "course.assessment.submission.FileInput.uploadDisabled": { "defaultMessage": "File upload disabled" }, "course.assessment.submission.FileInput.uploadLabel": { "defaultMessage": "Drag and drop or click to upload files" }, + "course.assessment.submission.GetHelpChatPage": { + "defaultMessage": "Get Help" + }, + "course.assessment.submission.GetHelpChatPage.ConversationArea.fileNameAndLineNumber": { + "defaultMessage": "{filename}:{lineNumber}" + }, + "course.assessment.submission.GetHelpChatPage.ConversationArea.lineNumber": { + "defaultMessage": "Line {lineNumber}" + }, + "course.assessment.submission.GetHelpChatPage.ConversationArea.threadExpired": { + "defaultMessage": "The chat above has ended. Start a new chat?" + }, + "course.assessment.submission.GetHelpChatPage.chatInputText": { + "defaultMessage": "How can we help you?" + }, + "course.assessment.submission.GetHelpChatPage.chatMessagesRemaining": { + "defaultMessage": "{numMessages} / {maxMessages} {numMessages, plural, one {message} other {messages}} remaining" + }, + "course.assessment.submission.GetHelpChatPage.codeUpdated": { + "defaultMessage": "Code Updated" + }, + "course.assessment.submission.GetHelpChatPage.endOfConversation": { + "defaultMessage": "View code after conversation" + }, + "course.assessment.submission.GetHelpChatPage.failedSyncingWithCodaveri": { + "defaultMessage": "Unavailable" + }, + "course.assessment.submission.GetHelpChatPage.noChatMessagesRemaining": { + "defaultMessage": "You have reached the message limit for this question." + }, + "course.assessment.submission.GetHelpChatPage.syncedWithCodaveri": { + "defaultMessage": "Ready" + }, + "course.assessment.submission.GetHelpChatPage.syncingWithCodaveri": { + "defaultMessage": "Preparing" + }, + "course.assessment.submission.ImportedFileView.delete": { + "defaultMessage": "Delete" + }, "course.assessment.submission.ImportedFileView.deleteConfirmation": { - "defaultMessage": "Are you sure you want to delete this file?" + "defaultMessage": "Are you sure you want to delete \"{fileName}\"?" + }, + "course.assessment.submission.ImportedFileView.deleteTitle": { + "defaultMessage": "Delete File" }, "course.assessment.submission.ImportedFileView.noFiles": { "defaultMessage": "No files uploaded." @@ -3114,17 +3686,11 @@ "course.assessment.submission.ImportedFileView.uploadedFiles": { "defaultMessage": "Uploaded Files:" }, - "course.assessment.submission.Answer.missingAnswer": { - "defaultMessage": "There is no answer submitted for this question - this might be caused by the addition of this question after the submission is submitted." - }, - "course.assessment.submission.answers.AnswerHeader.noPastAnswers": { - "defaultMessage": "No past answers." - }, - "course.assessment.submission.Answer.rendererNotImplemented": { - "defaultMessage": "The display for this question type has not been implemented yet." + "course.assessment.submission.SubmissionEditIndex.TimeLimitBanner.hoursMinutesSeconds": { + "defaultMessage": "{hrs, plural, one {# hour} other {# hours}} {mins, plural, =0 {} one {# minute} other {# minutes}} {secs, plural, =0 {} one {# second} other {# seconds}}" }, - "course.assessment.submission.SubmissionAnswer.viewPastAnswers": { - "defaultMessage": "Past Answers" + "course.assessment.submission.SubmissionEditIndex.TimeLimitBanner.minutesSeconds": { + "defaultMessage": "{secs, plural, one {# second} other {# seconds}}" }, "course.assessment.submission.SubmissionsIndex.accessLogs": { "defaultMessage": "Access Logs" @@ -3159,6 +3725,9 @@ "course.assessment.submission.SubmissionsIndex.experiencePoints": { "defaultMessage": "EXP Awarded" }, + "course.assessment.submission.SubmissionsIndex.fetchFromKoditsu": { + "defaultMessage": "Fetch Submissions from Koditsu" + }, "course.assessment.submission.SubmissionsIndex.forceSubmit": { "defaultMessage": "Force Submit Remaining" }, @@ -3171,12 +3740,12 @@ "course.assessment.submission.SubmissionsIndex.includePhantoms": { "defaultMessage": "Include phantom users" }, - "lib.translations.myStudents": { - "defaultMessage": "My Students" - }, "course.assessment.submission.SubmissionsIndex.phantom": { "defaultMessage": "Phantom User" }, + "course.assessment.submission.SubmissionsIndex.publishAutoFeedback": { + "defaultMessage": "Publish Automated Programming Feedback ({count})" + }, "course.assessment.submission.SubmissionsIndex.publishGrades": { "defaultMessage": "Publish Grades" }, @@ -3186,21 +3755,6 @@ "course.assessment.submission.SubmissionsIndex.remind": { "defaultMessage": "Send Reminder Emails" }, - "lib.translations.staff": { - "defaultMessage": "Staff" - }, - "lib.translations.students": { - "defaultMessage": "Students" - }, - "lib.translations.myStudentsIncludingPhantoms": { - "defaultMessage": "My Students (Including Phantoms)" - }, - "lib.translations.studentsIncludingPhantoms": { - "defaultMessage": "Students (Including Phantoms)" - }, - "lib.translations.staffIncludingPhantoms": { - "defaultMessage": "Staff (Including Phantoms)" - }, "course.assessment.submission.SubmissionsIndex.submissionStatus": { "defaultMessage": "Status" }, @@ -3216,6 +3770,9 @@ "course.assessment.submission.SubmissionsIndex.userName": { "defaultMessage": "Name" }, + "course.assessment.submission.TestCaseView.allFailed": { + "defaultMessage": "All failed" + }, "course.assessment.submission.TestCaseView.allPassed": { "defaultMessage": "All passed" }, @@ -3255,8 +3812,17 @@ "course.assessment.submission.TestCaseView.standardOutput": { "defaultMessage": "Standard Output" }, + "course.assessment.submission.TestCaseView.testCasesPassed": { + "defaultMessage": "{numPassed}/{numTestCases} passed" + }, "course.assessment.submission.UploadedFileView.deleteConfirmation": { - "defaultMessage": "Are you sure you want to delete this attachment?" + "defaultMessage": "Are you sure you want to delete {fileName}?" + }, + "course.assessment.submission.UploadedFileView.deleteTitle": { + "defaultMessage": "Delete File" + }, + "course.assessment.submission.UploadedFileView.deleting": { + "defaultMessage": "Delete" }, "course.assessment.submission.UploadedFileView.noFiles": { "defaultMessage": "No files uploaded." @@ -3265,7 +3831,7 @@ "defaultMessage": "Uploaded Files" }, "course.assessment.submission.VoiceResponseAnswer.chooseVoiceFileExplain": { - "defaultMessage": "Drag your audio file here, or click to select an audio file. Only wav and mp3 formats are supported. Alternatively, you may use the recorder below to record your response" + "defaultMessage": "Drag and drop or click to upload your WAV / MP3 files. Alternatively, use the recorder below to record your response" }, "course.assessment.submission.VoiceResponseAnswer.pleaseRecordYourVoice": { "defaultMessage": "Please record your voice" @@ -3390,6 +3956,24 @@ "course.assessment.submission.answerSubmitted": { "defaultMessage": "Answer Submitted" }, + "course.assessment.submission.answerTooLarge": { + "defaultMessage": "Answer Too Large" + }, + "course.assessment.submission.answerTooLargeError": { + "defaultMessage": "Your answer must be less than 2 MB." + }, + "course.assessment.submission.answers.AnswerHeader.noPastAnswers": { + "defaultMessage": "No past answers." + }, + "course.assessment.submission.answers.AnswerHeader.viewAllAnswers": { + "defaultMessage": "All Answers ({count})" + }, + "course.assessment.submission.answers.AnswerHeader.viewGetHelpHistory": { + "defaultMessage": "Get Help History ({count})" + }, + "course.assessment.submission.answers.AnswerHeader.viewPastAnswers": { + "defaultMessage": "Past Answers ({count})" + }, "course.assessment.submission.answers.ForumPostResponse.ForumCard.forumCardTitleTypeNoneSelected": { "defaultMessage": "Forum" }, @@ -3462,17 +4046,11 @@ "course.assessment.submission.answers.ForumPostResponse.TopicCard.viewTopicInNewTab": { "defaultMessage": "View Topic" }, - "course.assessment.submission.answers.Programming.ProgrammingFile.downloadFile": { - "defaultMessage": "Download File" - }, "course.assessment.submission.answers.Programming.ProgrammingFile.sizeTooBig": { "defaultMessage": "The file is too big and cannot be displayed." }, - "course.assessment.submission.answerTooLarge": { - "defaultMessage": "Answer Too Large" - }, - "course.assessment.submission.answerTooLargeError": { - "defaultMessage": "Your answer must be less than 2 MB." + "course.assessment.submission.attachmentRequired": { + "defaultMessage": "*please upload AT LEAST 1 file for this question" }, "course.assessment.submission.attemptedAt": { "defaultMessage": "Attempted At" @@ -3495,14 +4073,17 @@ "course.assessment.submission.bonusEndAt": { "defaultMessage": "Bonus End At" }, - "course.assessment.submission.codaveriAutogradeFailure": { - "defaultMessage": "There is an error while evaluating your code in Codaveri. Try submitting your code again in a couple of minutes or check the error message in the network response." + "course.assessment.submission.category": { + "defaultMessage": "Category" }, - "course.assessment.submission.liveFeedbackNoneGenerated": { - "defaultMessage": "Question {questionIndex}: No feedback generated." + "course.assessment.submission.checkAnswer": { + "defaultMessage": "Check Answer" }, - "course.assessment.submission.liveFeedbackSuccess": { - "defaultMessage": "Question {questionIndex}: Feedback successfully generated." + "course.assessment.submission.checkAnswerWithLimit": { + "defaultMessage": "Check Answer ({attemptsLeft, plural, one {# attempt} other {# attempts}} left)" + }, + "course.assessment.submission.codaveriAutogradeFailure": { + "defaultMessage": "There is an error while evaluating your code in Codaveri. Try submitting your code again in a couple of minutes or check the error message in the network response." }, "course.assessment.submission.comment.CodaveriCommentCard.finalise": { "defaultMessage": "Finalise and Post Feedback" @@ -3528,14 +4109,14 @@ "course.assessment.submission.comment.CommentCard.deleteConfirmation": { "defaultMessage": "Are you sure you want to delete this comment?" }, - "course.assessment.submission.comment.CommentCard.save": { - "defaultMessage": "Save" + "course.assessment.submission.comment.CommentCard.isAiGenerated": { + "defaultMessage": "AI Generated Comment" }, "course.assessment.submission.comment.CommentCard.publish": { "defaultMessage": "Publish" }, - "course.assessment.submission.comment.CommentCard.isAiGenerated": { - "defaultMessage": "AI Generated Comment" + "course.assessment.submission.comment.CommentCard.save": { + "defaultMessage": "Save" }, "course.assessment.submission.comment.CommentField.comment": { "defaultMessage": "Comment" @@ -3552,18 +4133,6 @@ "course.assessment.submission.comments": { "defaultMessage": "Comments" }, - "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemDelete": { - "defaultMessage": "Dismiss" - }, - "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemDislike": { - "defaultMessage": "Dislike" - }, - "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemLike": { - "defaultMessage": "Like" - }, - "course.assessment.submission.answers.Programming.ProgrammingFiles.liveFeedbackItemLineHeading": { - "defaultMessage": "Line {linenum}" - }, "course.assessment.submission.continue": { "defaultMessage": "Continue" }, @@ -3609,14 +4178,29 @@ "course.assessment.submission.emptyAssessment": { "defaultMessage": "This assessment currently has no questions." }, + "course.assessment.submission.errorUnknown": { + "defaultMessage": "Error is Unknown" + }, "course.assessment.submission.examDialogMessage": { "defaultMessage": "Please do not sign out or close the browser, otherwise you may have trouble continuing the exam." }, - "course.assessment.submission.examDialogTitle": { - "defaultMessage": "You are entering an exam." + "course.assessment.submission.examDialogTitle": { + "defaultMessage": "You are entering an exam." + }, + "course.assessment.submission.expAwarded": { + "defaultMessage": "EXP Awarded" + }, + "course.assessment.submission.explanation": { + "defaultMessage": "Explanation" + }, + "course.assessment.submission.fetchSubmissionsFromKoditsuConfirmation": { + "defaultMessage": "Are you sure you want to fetch all submissions from Koditsu? all the existing answers here will be overwritten by the newer one. NOTE THAT THIS ACTION IS IRREVERSIBLE!" + }, + "course.assessment.submission.fetchSubmissionsFromKoditsuPending": { + "defaultMessage": "Please wait as the submissions are currently being fetched from Koditsu." }, - "course.assessment.submission.expAwarded": { - "defaultMessage": "EXP Awarded" + "course.assessment.submission.fetchSubmissionsFromKoditsuSuccess": { + "defaultMessage": "All submissions have been fetched successfully from Koditsu" }, "course.assessment.submission.finalise": { "defaultMessage": "Finalise all answers" @@ -3648,6 +4232,9 @@ "course.assessment.submission.grade": { "defaultMessage": "Grade" }, + "course.assessment.submission.gradeDisplay": { + "defaultMessage": "Grade: {grade}" + }, "course.assessment.submission.gradePrefilled": { "defaultMessage": "Pre-filled" }, @@ -3657,9 +4244,6 @@ "course.assessment.submission.gradeSummary": { "defaultMessage": "Grade Summary" }, - "course.assessment.submission.gradeUnsaved": { - "defaultMessage": "Unsaved" - }, "course.assessment.submission.gradeUnsavedHint": { "defaultMessage": "This grade is not yet saved. Click Save Grade at the end of the page to save all grade changes." }, @@ -3675,6 +4259,12 @@ "course.assessment.submission.group": { "defaultMessage": "Group" }, + "course.assessment.submission.history.questionTitle": { + "defaultMessage": "Question Details" + }, + "course.assessment.submission.history.title": { + "defaultMessage": "Submission by {studentName}, Question {number}" + }, "course.assessment.submission.importFilesFailure": { "defaultMessage": "File uploads failed: {errors}" }, @@ -3684,9 +4274,24 @@ "course.assessment.submission.invalidFileUpload": { "defaultMessage": "File uploads failed: Only java files can be uploaded" }, + "course.assessment.submission.isSaved": { + "defaultMessage": "Saved" + }, + "course.assessment.submission.isSaving": { + "defaultMessage": "Saving" + }, + "course.assessment.submission.isUnsaved": { + "defaultMessage": "Unsaved" + }, "course.assessment.submission.lateSubmission": { "defaultMessage": "This submission is LATE! You may want to penalize the student for late submission." }, + "course.assessment.submission.liveFeedbackHistory.codeHistory": { + "defaultMessage": "Code History" + }, + "course.assessment.submission.liveFeedbackNoneGenerated": { + "defaultMessage": "No feedback generated." + }, "course.assessment.submission.loadingComment": { "defaultMessage": "Loading comment field..." }, @@ -3723,6 +4328,9 @@ "course.assessment.submission.mark": { "defaultMessage": "Submit for Publishing" }, + "course.assessment.submission.max": { + "defaultMessage": "Max" + }, "course.assessment.submission.maximumGroupGrade": { "defaultMessage": "Maximum Grade for this Group" }, @@ -3735,6 +4343,9 @@ "course.assessment.submission.ok": { "defaultMessage": "OK" }, + "course.assessment.submission.onlyOneAttachmentAllowed": { + "defaultMessage": "*ONLY 1 file is allowed for this question" + }, "course.assessment.submission.pastAnswers": { "defaultMessage": "Past Answers" }, @@ -3777,14 +4388,20 @@ "course.assessment.submission.question": { "defaultMessage": "Question" }, - "course.assessment.submission.questionNumber": { - "defaultMessage": "Q{number}" + "course.assessment.submission.questionAnswer": { + "defaultMessage": "Answer" }, "course.assessment.submission.questionDescription": { "defaultMessage": "Description" }, - "course.assessment.submission.questionAnswer": { - "defaultMessage": "Answer" + "course.assessment.submission.questionHeading": { + "defaultMessage": "Question {number}" + }, + "course.assessment.submission.questionHeadingWithTitle": { + "defaultMessage": "Question {number}: {title}" + }, + "course.assessment.submission.questionNumber": { + "defaultMessage": "Q{number}" }, "course.assessment.submission.readOnlyEditor.expandComments": { "defaultMessage": "Expand all comments" @@ -3795,6 +4412,12 @@ "course.assessment.submission.reevaluate": { "defaultMessage": "Re-evaluate Answer" }, + "course.assessment.submission.remainingBufferTime": { + "defaultMessage": "Finalising in: {timeLimit}" + }, + "course.assessment.submission.remainingTime": { + "defaultMessage": "Time Remaining: {timeLimit}" + }, "course.assessment.submission.rendererNotImplemented": { "defaultMessage": "The display for this question type has not been implemented yet." }, @@ -3807,14 +4430,8 @@ "course.assessment.submission.resetConfirmation": { "defaultMessage": "Are you sure you want to reset your answer? This action is irreversible and you will lose all your current work for this question." }, - "course.assessment.submission.checkAnswer": { - "defaultMessage": "Check Answer" - }, - "course.assessment.submission.checkAnswerWithLimit": { - "defaultMessage": "Check Answer ({attemptsLeft, plural, one {# attempt} other {# attempts}} left)" - }, - "course.assessment.submission.submitWithLimit": { - "defaultMessage": "Submit ({attemptsLeft, plural, one {# attempt} other {# attempts}} left)" + "course.assessment.submission.rubricScores": { + "defaultMessage": "Rubric Grades" }, "course.assessment.submission.saveDraft": { "defaultMessage": "Save Draft" @@ -3822,6 +4439,15 @@ "course.assessment.submission.saveGrade": { "defaultMessage": "Save Grade" }, + "course.assessment.submission.saved": { + "defaultMessage": "Saved" + }, + "course.assessment.submission.saving": { + "defaultMessage": "Saving" + }, + "course.assessment.submission.savingFailed": { + "defaultMessage": "Saving Failed" + }, "course.assessment.submission.sendReminderEmailConfirmation": { "defaultMessage": "Send reminder emails to {unattempted} unattempted and {attempting} attempting user(s) ({selectedUsers}) who have not completed the assessment?" }, @@ -3858,6 +4484,9 @@ "course.assessment.submission.submissionBy": { "defaultMessage": "Submission by {name}" }, + "course.assessment.submission.submissionError": { + "defaultMessage": "There is a problem in submitting question for {questions}" + }, "course.assessment.submission.submissionsHeader": { "defaultMessage": "Submissions: {assessment}" }, @@ -3870,14 +4499,47 @@ "course.assessment.submission.submitShortcut": { "defaultMessage": "(Ctrl+Enter) or (⌘+Enter)" }, + "course.assessment.submission.submitWithLimit": { + "defaultMessage": "Submit ({attemptsLeft, plural, one {# attempt} other {# attempts}} left)" + }, "course.assessment.submission.submitted": { "defaultMessage": "Submitted" }, "course.assessment.submission.submittedAt": { "defaultMessage": "Submitted At" }, - "course.assessment.submission.unknown": { - "defaultMessage": "Unknown status, please contact administrator" + "course.assessment.submission.suggestions.howDoIFixThis": { + "defaultMessage": "How do I fix this?" + }, + "course.assessment.submission.suggestions.iAmStuck": { + "defaultMessage": "I am stuck" + }, + "course.assessment.submission.suggestions.looksWrong": { + "defaultMessage": "This looks wrong" + }, + "course.assessment.submission.suggestions.optimizeThisCode": { + "defaultMessage": "Review my code" + }, + "course.assessment.submission.suggestions.questionUnclear": { + "defaultMessage": "Explain the question" + }, + "course.assessment.submission.suggestions.whereAmIWrong": { + "defaultMessage": "Where am I wrong?" + }, + "course.assessment.submission.timeIsUp": { + "defaultMessage": "Time is Up!" + }, + "course.assessment.submission.timedAssessmentDialogMessage": { + "defaultMessage": "{stillSomeTimeRemaining, select, true {Once the time is up, the assessment will be automatically finalised.} other {Finalising the submission now!}}" + }, + "course.assessment.submission.timedAssessmentDialogTitle": { + "defaultMessage": "{stillSomeTimeRemaining, select, true {{remainingTime} {isNewSubmission, select, true {} other {remaining}} to complete this assessment.} other {The assessment has ended!}}" + }, + "course.assessment.submission.timedExamDialogMessage": { + "defaultMessage": "{stillSomeTimeRemaining, select, true {Please do not sign out or close the browser while attempting this exam. Once the time is up, the assessment will be automatically finalised.} other {Finalising the submission now!}}" + }, + "course.assessment.submission.timedExamDialogTitle": { + "defaultMessage": "{stillSomeTimeRemaining, select, true {{remainingTime} {isNewSubmission, select, true {} other {remaining}} to complete this exam.} other {The exam has ended!}}" }, "course.assessment.submission.totalGrade": { "defaultMessage": "Total Grade" @@ -3885,6 +4547,9 @@ "course.assessment.submission.type": { "defaultMessage": "Type" }, + "course.assessment.submission.unknown": { + "defaultMessage": "Unknown status, please contact administrator" + }, "course.assessment.submission.unmark": { "defaultMessage": "Revert to Submitted" }, @@ -3898,7 +4563,7 @@ "defaultMessage": "Unsubmit Submission" }, "course.assessment.submission.unsubmitAllConfirmation": { - "defaultMessage": "Are you sure you want to UNSUBMIT the submissions for all {users}? All submissions will be unsubmitted and this will reset the submission time and permit the users to change their answers. NOTE THAT THIS ACTION IS IRREVERSIBLE" + "defaultMessage": "Are you sure you want to UNSUBMIT the submissions for all {users}? All submissions will be unsubmitted and this will reset the submission time and permit the users to change their submissions. NOTE THAT THIS ACTION IS IRREVERSIBLE" }, "course.assessment.submission.unsubmitAllSubmissionsJobPending": { "defaultMessage": "Please wait as the submissions are currently being unsubmitted." @@ -3915,6 +4580,9 @@ "course.assessment.submission.updateFailure": { "defaultMessage": "Submission update failed: {errors}" }, + "course.assessment.submission.updateIndividualSuccess": { + "defaultMessage": "Submission for {errors} updated successfully" + }, "course.assessment.submission.updateSuccess": { "defaultMessage": "Submission updated successfully." }, @@ -3951,9 +4619,6 @@ "course.assessment.submissions.SubmissionsIndex.header": { "defaultMessage": "Submissions" }, - "course.assessment.submission.SubmissionsIndex.publishAutoFeedback": { - "defaultMessage": "Publish Automated Programming Feedback ({count})" - }, "course.assessment.submissions.SubmissionsTable.gradeTooltip": { "defaultMessage": "These grades can't be seen by the student until they are published" }, @@ -3976,7 +4641,7 @@ "defaultMessage": "Submitted At" }, "course.assessment.submissions.SubmissionsTable.tableHeaderTitle": { - "defaultMessage": "Assessment" + "defaultMessage": "Title" }, "course.assessment.submissions.SubmissionsTable.tableHeaderTotalGrade": { "defaultMessage": "Grade" @@ -4035,6 +4700,18 @@ "course.assessments.index.hasTodo": { "defaultMessage": "Has TODO" }, + "course.assessments.index.inviteToKoditsu": { + "defaultMessage": "Invite users to Koditsu Exam" + }, + "course.assessments.index.invitingUserToKoditsu": { + "defaultMessage": "Inviting users to Koditsu Exam" + }, + "course.assessments.index.invitingUserToKoditsuFailure": { + "defaultMessage": "There is a problem in inviting users to Koditsu. Please try again later" + }, + "course.assessments.index.invitingUserToKoditsuSuccess": { + "defaultMessage": "Successful in inviting users to Koditsu Exam" + }, "course.assessments.index.neededFor": { "defaultMessage": "Needed for" }, @@ -4065,6 +4742,9 @@ "course.assessments.index.submittedCount": { "defaultMessage": "Submissions" }, + "course.assessments.index.timeLimitIcon": { + "defaultMessage": "Time Limit: {timeLimit, plural, one {# minute} other {# minutes}}" + }, "course.assessments.index.title": { "defaultMessage": "Title" }, @@ -4083,9 +4763,6 @@ "course.asssessment.submission.submitNoQuestionExplain": { "defaultMessage": "Mark as completed?" }, - "course.admin.NotificationSettings.component": { - "defaultMessage": "Component" - }, "course.componentTitles.course_achievements_component": { "defaultMessage": "Achievements" }, @@ -4170,15 +4847,6 @@ "course.courses.CourseAnnouncements.announcementHeader": { "defaultMessage": "Latest announcements" }, - "course.courses.CourseSuspendedAlert.header": { - "defaultMessage": "This course is suspended. Instructors can still access it, but students cannot." - }, - "course.courses.CourseSuspendedAlert.canSuspendMessage": { - "defaultMessage": "You can unsuspend it from the {link} page." - }, - "course.courses.CourseSuspendedAlert.cannotSuspendMessage": { - "defaultMessage": "If you believe this is a mistake, contact a course manager or owner to have them unsuspend the course." - }, "course.courses.CourseDisplay.noCourse": { "defaultMessage": "There is no course yet..." }, @@ -4224,6 +4892,15 @@ "course.courses.CourseShow.instructorsHeader": { "defaultMessage": "Instructors" }, + "course.courses.CourseSuspendedAlert.canSuspendMessage": { + "defaultMessage": "You can unsuspend it from the {link} page." + }, + "course.courses.CourseSuspendedAlert.cannotSuspendMessage": { + "defaultMessage": "If you believe this is a mistake, contact a course manager or owner to have them unsuspend the course." + }, + "course.courses.CourseSuspendedAlert.header": { + "defaultMessage": "This course is suspended. Instructors can still access it, but students cannot." + }, "course.courses.CourseUserItem.differentCourseNameHint": { "defaultMessage": "You're seeing a name different from your account name because this course's manager invited you with this name." }, @@ -4339,7 +5016,7 @@ "defaultMessage": "Starts at" }, "course.courses.PendingTodosTable.tableHeaderTitle": { - "defaultMessage": "Title" + "defaultMessage": "Assessment" }, "course.courses.PendingTodosTable.tableSeeMore": { "defaultMessage": "See {n} more" @@ -4347,6 +5024,9 @@ "course.courses.Sidebar.administration": { "defaultMessage": "Administration" }, + "course.courses.Sidebar.joinCoursemologyMessage": { + "defaultMessage": "Create a Coursemology account or sign up to join this course." + }, "course.courses.SidebarItem.admin.duplication": { "defaultMessage": "Duplicate Data" }, @@ -4392,15 +5072,15 @@ "course.courses.SidebarItem.home": { "defaultMessage": "Home" }, + "course.courses.SidebarItem.scholaistic.assessments": { + "defaultMessage": "Role-Playing Assessments" + }, "course.courses.SidebarItem.stories.learn": { "defaultMessage": "Learn" }, "course.courses.SidebarItem.stories.missionControl": { "defaultMessage": "Mission Control" }, - "course.courses.SidebarItem.scholaistic.assessments": { - "defaultMessage": "Role-Playing Assessments" - }, "course.courses.TodoIgnoreButton.ignore.ignoreButtonText": { "defaultMessage": "Ignore" }, @@ -4443,6 +5123,12 @@ "course.discussion.topics.CommentCard.deleteSuccess": { "defaultMessage": "Successfully deleted comment." }, + "course.discussion.topics.CommentCard.isAiGenerated": { + "defaultMessage": "AI Generated Comment" + }, + "course.discussion.topics.CommentCard.publish": { + "defaultMessage": "Publish" + }, "course.discussion.topics.CommentCard.publishFailure": { "defaultMessage": "Failed to publish feedback." }, @@ -4464,12 +5150,6 @@ "course.discussion.topics.CommentCard.updateSuccess": { "defaultMessage": "Successfully updated comment." }, - "course.discussion.topics.CommentCard.publish": { - "defaultMessage": "Publish" - }, - "course.discussion.topics.CommentCard.isAiGenerated": { - "defaultMessage": "AI Generated Comment" - }, "course.discussion.topics.CommentField.comment": { "defaultMessage": "Comment" }, @@ -4537,7 +5217,7 @@ "defaultMessage": "Select current instance" }, "course.duplication.Duplication.DestinationCourseSelector.InstanceDropdown.destinationInstance": { - "defaultMessage": "Destination Instance" + "defaultMessage": "Destination instance" }, "course.duplication.Duplication.DestinationCourseSelector.NewCourseForm.newStartAt": { "defaultMessage": "New Start Date *" @@ -4641,15 +5321,15 @@ "course.duplication.Duplication.duplicateData": { "defaultMessage": "Duplicate Data" }, - "course.duplication.Duplication.fromCourse": { - "defaultMessage": "Duplicate Data from {courseTitle}" - }, "course.duplication.Duplication.duplicationDisabled": { "defaultMessage": "Duplication is disabled for this course." }, "course.duplication.Duplication.existingCourse": { "defaultMessage": "Existing Course" }, + "course.duplication.Duplication.fromCourse": { + "defaultMessage": "Duplicate data from {courseTitle}" + }, "course.duplication.Duplication.items": { "defaultMessage": "Selected Items" }, @@ -4743,21 +5423,6 @@ "course.enrolRequests.UserRequests.rejected": { "defaultMessage": "Rejected Enrolment Requests" }, - "course.experiencePoints.downloadCsvButton": { - "defaultMessage": "Download CSV" - }, - "course.experiencePoints.downloadFailure": { - "defaultMessage": "An error occurred while doing your request for download." - }, - "course.experiencePoints.downloadPending": { - "defaultMessage": "Please wait as your request to download is being processed." - }, - "course.experiencePoints.downloadRequestSuccess": { - "defaultMessage": "Your request to download is successful" - }, - "course.experiencePoints.filterByNameButton": { - "defaultMessage": "Filter by Name" - }, "course.experiencePoints.disbursement.DisbursementForm.createDisbursementFailure": { "defaultMessage": "Failed to award experience points." }, @@ -4789,7 +5454,7 @@ "defaultMessage": "Disburse Points" }, "course.experiencePoints.disbursement.DisbursementIndex.disbursements": { - "defaultMessage": "Disbursed Experience Points" + "defaultMessage": "Experience Points" }, "course.experiencePoints.disbursement.DisbursementIndex.experienceTab": { "defaultMessage": "History" @@ -4827,6 +5492,15 @@ "course.experiencePoints.disbursement.FilterForm.weeklyCap": { "defaultMessage": "Weekly Cap" }, + "course.experiencePoints.disbursement.ForumDisbursement.fetchDisbursementFailure": { + "defaultMessage": "Failed to retrieve data." + }, + "course.experiencePoints.disbursement.ForumDisbursement.fetchForumPostsFailure": { + "defaultMessage": "Failed to fetch forum posts." + }, + "course.experiencePoints.disbursement.ForumDisbursement.postListDialogHeader": { + "defaultMessage": "Posts created between {startDate} and {endDate} by" + }, "course.experiencePoints.disbursement.ForumDisbursementForm.createDisbursementFailure": { "defaultMessage": "Failed to award experience points." }, @@ -4836,9 +5510,6 @@ "course.experiencePoints.disbursement.ForumDisbursementForm.fetchForumPostsFailure": { "defaultMessage": "Failed to fetch forum posts." }, - "course.experiencePoints.disbursement.ForumDisbursementForm.postListDialogHeader": { - "defaultMessage": "Posts created between {startDate} and {endDate} by" - }, "course.experiencePoints.disbursement.ForumDisbursementForm.reason": { "defaultMessage": "Reason For Disbursement" }, @@ -4878,6 +5549,27 @@ "course.experiencePoints.disbursement.ForumPostTable.voteTally": { "defaultMessage": "Vote Tally" }, + "course.experiencePoints.disbursement.GeneralDisbursement.fetchDisbursementFailure": { + "defaultMessage": "Failed to retrieve data." + }, + "course.experiencePoints.downloadCsvButton": { + "defaultMessage": "Download CSV" + }, + "course.experiencePoints.downloadFailure": { + "defaultMessage": "An error occurred while doing your request for download." + }, + "course.experiencePoints.downloadPending": { + "defaultMessage": "Please wait as your request to download is being processed." + }, + "course.experiencePoints.downloadRequestSuccess": { + "defaultMessage": "Your request to download is successful" + }, + "course.experiencePoints.fetchRecordsFailure": { + "defaultMessage": "Failed to fetch records" + }, + "course.experiencePoints.filterByNameButton": { + "defaultMessage": "Filter by Name" + }, "course.forum.FormShow.fetchTopicsFailure": { "defaultMessage": "Failed to retrieve forum topic data." }, @@ -5115,48 +5807,57 @@ "course.forum.ForumsIndex.newForum": { "defaultMessage": "New Forum" }, + "course.forum.GenerateReplyButton.generateReply": { + "defaultMessage": "Generate reply" + }, + "course.forum.GenerateReplyButton.generateReplySuccess": { + "defaultMessage": "A reply has been successfully generated." + }, + "course.forum.GenerateReplyButton.generatingReply": { + "defaultMessage": "Generating reply" + }, "course.forum.HideButton.hide": { "defaultMessage": "Hide" }, - "course.forum.HideButton.hideTooltip": { - "defaultMessage": "Hide topic from students" - }, "course.forum.HideButton.hideFailure": { "defaultMessage": "Failed to hide the topic \"{title}\" - {error}" }, "course.forum.HideButton.hideSuccess": { "defaultMessage": "The topic \"{title}\" has successfully been hidden." }, + "course.forum.HideButton.hideTooltip": { + "defaultMessage": "Hide topic from students" + }, "course.forum.HideButton.unhide": { "defaultMessage": "Unhide" }, - "course.forum.HideButton.unhideTooltip": { - "defaultMessage": "Show topic to students" - }, "course.forum.HideButton.unhideFailure": { "defaultMessage": "Failed to unhide the topic \"{title}\" - {error}" }, "course.forum.HideButton.unhideSuccess": { "defaultMessage": "The topic \"{title}\" has successfully been unhidden." }, - "course.forum.LockButton.locked": { - "defaultMessage": "Lock" + "course.forum.HideButton.unhideTooltip": { + "defaultMessage": "Show topic to students" }, "course.forum.LockButton.lockTooltip": { "defaultMessage": "Lock to stop students from posting in this topic" }, + "course.forum.LockButton.locked": { + "defaultMessage": "Lock" + }, "course.forum.LockButton.lockedFailure": { "defaultMessage": "Failed to locked the topic \"{title}\" - {error}" }, "course.forum.LockButton.lockedSuccess": { "defaultMessage": "The topic \"{title}\" has successfully been locked." }, - "course.forum.LockButton.unlocked": { - "defaultMessage": "Unlock" - }, "course.forum.LockButton.unlockTooltip": { "defaultMessage": "Unlock to allow students to post within this topic" }, + "course.forum.LockButton.unlocked": { + "defaultMessage": "Unlock" + }, "course.forum.LockButton.unlockedFailure": { "defaultMessage": "Failed to unlocked the topic \"{title}\" - {error}" }, @@ -5175,6 +5876,12 @@ "course.forum.MarkAnswerButton.markAsAnswer": { "defaultMessage": "Mark as answer" }, + "course.forum.MarkAnswerButton.markAsAnswerAndPublish": { + "defaultMessage": "Mark as answer and publish" + }, + "course.forum.MarkAnswerButton.markAsAnswerAndPublishTooltip": { + "defaultMessage": "Mark as answer and publish for students to view" + }, "course.forum.MarkAnswerButton.markedAsAnswer": { "defaultMessage": "Marked as answer" }, @@ -5274,18 +5981,75 @@ "course.forum.forum.markAllAsReadFailed": { "defaultMessage": "Failed to mark all topics in this forum as read. Please try again later." }, - "course.gradebook.GradebookColumnTree.grades": { - "defaultMessage": "Grades" + "course.forum.publishButton.generateReplyDisabledTooltip": { + "defaultMessage": "Disabled for generated reply" + }, + "course.forum.publishButton.generateReplySuccess": { + "defaultMessage": "Failed to generate a reply." + }, + "course.forum.publishButton.generateReplyTooltip": { + "defaultMessage": "Generate a draft reply using AI" + }, + "course.forum.publishButton.publish": { + "defaultMessage": "Publish" + }, + "course.forum.publishButton.publishFailure": { + "defaultMessage": "Failed to publish the post." + }, + "course.forum.publishButton.publishSuccess": { + "defaultMessage": "The post has succesfully been published." + }, + "course.forum.publishButton.publishTooltip": { + "defaultMessage": "Pusblish post to students" + }, + "course.gradebook.ConfigureWeightsDialog.cancel": { + "defaultMessage": "Cancel" + }, + "course.gradebook.ConfigureWeightsDialog.description": { + "defaultMessage": "Set how much each tab contributes to the total grade. Weights should sum to 100." + }, + "course.gradebook.ConfigureWeightsDialog.errorInteger": { + "defaultMessage": "Value must be a whole number" + }, + "course.gradebook.ConfigureWeightsDialog.errorMax": { + "defaultMessage": "Value must be at most 100" + }, + "course.gradebook.ConfigureWeightsDialog.errorMin": { + "defaultMessage": "Value must be at least 0" + }, + "course.gradebook.ConfigureWeightsDialog.save": { + "defaultMessage": "Save" + }, + "course.gradebook.ConfigureWeightsDialog.saveFailure": { + "defaultMessage": "Failed to save weights — try again." + }, + "course.gradebook.ConfigureWeightsDialog.saveSuccess": { + "defaultMessage": "Weights saved." + }, + "course.gradebook.ConfigureWeightsDialog.sumWarning": { + "defaultMessage": "Weights do not sum to 100. Saving is allowed; Total may be inaccurate." + }, + "course.gradebook.ConfigureWeightsDialog.title": { + "defaultMessage": "Configure tab weights" + }, + "course.gradebook.ConfigureWeightsDialog.totalLabel": { + "defaultMessage": "Total: {sum}%" + }, + "course.gradebook.GradebookColumnTree.alwaysIncluded": { + "defaultMessage": "Always included" }, "course.gradebook.GradebookColumnTree.email": { "defaultMessage": "Email" }, + "course.gradebook.GradebookColumnTree.gamification": { + "defaultMessage": "Gamification" + }, + "course.gradebook.GradebookColumnTree.grades": { + "defaultMessage": "Grades" + }, "course.gradebook.GradebookColumnTree.level": { "defaultMessage": "Level" }, - "course.gradebook.GradebookColumnTree.alwaysIncluded": { - "defaultMessage": "Always included" - }, "course.gradebook.GradebookColumnTree.name": { "defaultMessage": "Name" }, @@ -5295,8 +6059,14 @@ "course.gradebook.GradebookColumnTree.totalXp": { "defaultMessage": "Total XP" }, - "course.gradebook.GradebookColumnTree.gamification": { - "defaultMessage": "Gamification" + "course.gradebook.GradebookIndex.allAssessments": { + "defaultMessage": "All assessments" + }, + "course.gradebook.GradebookIndex.applyAndExport": { + "defaultMessage": "Apply and Export" + }, + "course.gradebook.GradebookIndex.byWeight": { + "defaultMessage": "By weight" }, "course.gradebook.GradebookIndex.dialogTitle": { "defaultMessage": "Select columns" @@ -5310,33 +6080,60 @@ "course.gradebook.GradebookIndex.exportRows": { "defaultMessage": "Export {count, plural, one {# row} other {# rows}}" }, - "course.gradebook.GradebookIndex.selectColumns": { - "defaultMessage": "Select Columns" - }, - "course.gradebook.GradebookIndex.applyAndExport": { - "defaultMessage": "Apply and Export" - }, "course.gradebook.GradebookIndex.fetchFailure": { "defaultMessage": "Failed to retrieve Gradebook." }, "course.gradebook.GradebookIndex.gradebook": { "defaultMessage": "Gradebook" }, - "course.gradebook.GradebookIndex.searchStudents": { - "defaultMessage": "Search by name or email" - }, "course.gradebook.GradebookIndex.noStudents": { "defaultMessage": "No students enrolled yet" }, "course.gradebook.GradebookIndex.noStudentsHint": { "defaultMessage": "Grades will appear here once students join the course." }, + "course.gradebook.GradebookIndex.searchStudents": { + "defaultMessage": "Search by name or email" + }, + "course.gradebook.GradebookIndex.selectColumns": { + "defaultMessage": "Select Columns" + }, "course.gradebook.GradebookTable.maxMarks": { "defaultMessage": "Max Marks" }, "course.gradebook.GradebookTable.noDataColumnsHint": { "defaultMessage": "No grade columns selected - export will include student info only." }, + "course.gradebook.GradebookTable.noDataColumnsHintWithGamification": { + "defaultMessage": "No grade or gamification columns selected - export will include student info only." + }, + "course.gradebook.GradebookWeightedTable.configure": { + "defaultMessage": "Configure Weights" + }, + "course.gradebook.GradebookWeightedTable.emptyStateBody": { + "defaultMessage": "Click Configure Weights to start." + }, + "course.gradebook.GradebookWeightedTable.emptyStateTitle": { + "defaultMessage": "No tab weights configured." + }, + "course.gradebook.GradebookWeightedTable.name": { + "defaultMessage": "Name" + }, + "course.gradebook.GradebookWeightedTable.sumWarningTooltip": { + "defaultMessage": "Tab weights sum to {sum}%. Configure Weights to fix." + }, + "course.gradebook.GradebookWeightedTable.total": { + "defaultMessage": "Total" + }, + "course.gradebook.GradebookWeightedTable.totalSubheader": { + "defaultMessage": "{sum}% total" + }, + "course.gradebook.GradebookWeightedTable.treatUngradedAsZero": { + "defaultMessage": "Treat Ungraded as 0" + }, + "course.gradebook.GradebookWeightedTable.weightSubheader": { + "defaultMessage": "{weight}% of grade" + }, "course.group.GroupCreationForm.description": { "defaultMessage": "Description (Optional)" }, @@ -5613,27 +6410,27 @@ "course.leaderboard.LeaderboardTable.average": { "defaultMessage": "Average" }, - "course.leaderboard.LeaderboardTable.experience": { - "defaultMessage": "Experience" + "course.leaderboard.LeaderboardTable.averageAchievements": { + "defaultMessage": "Average Achievements" }, - "course.leaderboard.LeaderboardTable.rank": { - "defaultMessage": "Rank" + "course.leaderboard.LeaderboardTable.averageExperience": { + "defaultMessage": "Average Experience" }, - "course.leaderboard.LeaderboardTable.name": { - "defaultMessage": "Name" + "course.leaderboard.LeaderboardTable.experience": { + "defaultMessage": "Experience" }, "course.leaderboard.LeaderboardTable.level": { "defaultMessage": "Level" }, - "course.leaderboard.LeaderboardTable.averageExperience": { - "defaultMessage": "Average Experience" - }, - "course.leaderboard.LeaderboardTable.averageAchievements": { - "defaultMessage": "Average Achievements" - }, "course.leaderboard.LeaderboardTable.members": { "defaultMessage": "Members" }, + "course.leaderboard.LeaderboardTable.name": { + "defaultMessage": "Name" + }, + "course.leaderboard.LeaderboardTable.rank": { + "defaultMessage": "Rank" + }, "course.leaderboard.LeaderboardTable.titleAchievements": { "defaultMessage": "By Achievements" }, @@ -5802,20 +6599,56 @@ "course.level.Level.levelHeader": { "defaultMessage": "Levels" }, - "course.level.Level.saveFailure": { - "defaultMessage": "Level saving failed, please try again." + "course.level.Level.orderedIncorrectly": { + "defaultMessage": "Levels will be sorted automatically when saved regardless of their order here." + }, + "course.level.Level.placeholder": { + "defaultMessage": "0" + }, + "course.level.Level.reset": { + "defaultMessage": "Reset" + }, + "course.level.Level.resetTooltip": { + "defaultMessage": "Reset changes" + }, + "course.level.Level.saveChanges": { + "defaultMessage": "Save" }, - "course.level.Level.saveLevels": { - "defaultMessage": "Save Levels" + "course.level.Level.saveFailure": { + "defaultMessage": "Failed to save levels" }, "course.level.Level.saveSuccess": { "defaultMessage": "Levels Saved" }, "course.level.Level.thresholdHeader": { - "defaultMessage": "Threshold" + "defaultMessage": "EXP Threshold" + }, + "course.level.Level.unsavedChanges": { + "defaultMessage": "You have unsaved changes" + }, + "course.material.files.DownloadingFilePage.clickToDownloadFile": { + "defaultMessage": "Download {name}" + }, + "course.material.files.DownloadingFilePage.clickToDownloadFileDescription": { + "defaultMessage": "Something happened when initiating an automatic download. Click the link below to immediately download the file." + }, + "course.material.files.DownloadingFilePage.downloading": { + "defaultMessage": "Downloading {name}" + }, + "course.material.files.DownloadingFilePage.downloadingDescription": { + "defaultMessage": "This file should start downloading automatically now. If it doesn't, you can try again by clicking the link below or refreshing this page." + }, + "course.material.files.DownloadingFilePage.tryDownloadingAgain": { + "defaultMessage": "Try downloading again" + }, + "course.material.files.ErrorRetrievingFilePage.goToTheWorkbin": { + "defaultMessage": "Go to the Workbin" }, - "course.level.LevelRow.zeroThresholdError": { - "defaultMessage": "Experience points threshold cannot be 0" + "course.material.files.ErrorRetrievingFilePage.problemRetrievingFile": { + "defaultMessage": "Problem retrieving file" + }, + "course.material.files.ErrorRetrievingFilePage.problemRetrievingFileDescription": { + "defaultMessage": "Either it no longer exists, you don't have the permission to access it, or something unexpected happened when we were trying to retrieve it." }, "course.material.folders.DownloadFolderButton.downloadFolderErrorMessage": { "defaultMessage": "Download has failed. Please try again later." @@ -5826,6 +6659,15 @@ "course.material.folders.DownloadFolderButton.downloading": { "defaultMessage": "Downloading..." }, + "course.material.folders.ErrorRetrievingFolderPage.goToMainFolder": { + "defaultMessage": "Go to the main folder" + }, + "course.material.folders.ErrorRetrievingFolderPage.problemRetrievingFolder": { + "defaultMessage": "Problem retrieving folder" + }, + "course.material.folders.ErrorRetrievingFolderPage.problemRetrievingFolderDescription": { + "defaultMessage": "Either it no longer exists, you don't have the permission to access it, or something unexpected happened when we were trying to retrieve it." + }, "course.material.folders.FolderEdit.editSubfolderTitle": { "defaultMessage": "Edit Folder" }, @@ -5865,6 +6707,12 @@ "course.material.folders.FolderShow.defaultHeader": { "defaultMessage": "Materials" }, + "course.material.folders.FolderShow.error": { + "defaultMessage": "(Error)" + }, + "course.material.folders.FolderShow.folderNotFound": { + "defaultMessage": "Folder not found" + }, "course.material.folders.MaterialEdit.editMaterialTitle": { "defaultMessage": "Edit Material" }, @@ -5910,41 +6758,71 @@ "course.material.folders.UploadFilesButton.uploadFilesTooltip": { "defaultMessage": "Upload" }, + "course.material.folders.WorkbinTable.lastModified": { + "defaultMessage": "Last Modified" + }, + "course.material.folders.WorkbinTable.name": { + "defaultMessage": "Name" + }, + "course.material.folders.WorkbinTable.startAt": { + "defaultMessage": "Start At" + }, "course.material.folders.WorkbinTableButtons.DeletionFailure": { "defaultMessage": "could not be deleted" }, + "course.material.folders.WorkbinTableButtons.addFailure": { + "defaultMessage": "{material} could not be added to knowledge base" + }, "course.material.folders.WorkbinTableButtons.deleteConfirmation": { "defaultMessage": "Are you sure you want to delete" }, "course.material.folders.WorkbinTableButtons.deletionSuccess": { "defaultMessage": "has been deleted" }, + "course.material.folders.WorkbinTableButtons.removeFailure": { + "defaultMessage": "{material} could not be removed from knowledge base" + }, + "course.material.folders.WorkbinTableButtons.removeSuccess": { + "defaultMessage": "{material} has been removed from knowledge base" + }, "course.material.folders.WorkbinTableButtons.tableButtonDeleteTooltip": { "defaultMessage": "Delete" }, - "course.material.folders.WorkbinTable.name": { - "defaultMessage": "Name" + "course.plagiarism.PlagiarismIndex.assessments.AssessmentLinkDialog.linkAssessments": { + "defaultMessage": "Link Assessments" }, - "course.material.folders.WorkbinTable.lastModified": { - "defaultMessage": "Last Modified" + "course.plagiarism.PlagiarismIndex.assessments.AssessmentLinkDialog.linkedAssessments": { + "defaultMessage": "Linked Assessments" }, - "course.material.folders.WorkbinTable.startAt": { - "defaultMessage": "Start At" + "course.plagiarism.PlagiarismIndex.assessments.AssessmentLinkDialog.searchPlaceholder": { + "defaultMessage": "Search by Assessment Title" }, - "course.plagiarism.PlagiarismIndex.header.plagiarism": { - "defaultMessage": "Plagiarism Check" + "course.plagiarism.PlagiarismIndex.assessments.AssessmentLinkDialog.unlinkedAssessments": { + "defaultMessage": "Available Assessments" }, - "course.plagiarism.PlagiarismIndex.assessments.assessment": { - "defaultMessage": "Assessments" + "course.plagiarism.PlagiarismIndex.assessments.AssessmentLinkDialog.updateLinksFailure": { + "defaultMessage": "Failed to update assessment links" }, - "course.plagiarism.PlagiarismIndex.assessments.numSubmitted": { - "defaultMessage": "# Submissions" + "course.plagiarism.PlagiarismIndex.assessments.AssessmentLinkDialog.updateLinksSuccess": { + "defaultMessage": "Assessment links updated successfully" }, - "course.plagiarism.PlagiarismIndex.assessments.numCheckableQuestions": { - "defaultMessage": "# Checkable Questions" + "course.plagiarism.PlagiarismIndex.assessments.AssessmentLinkList.cannotManage": { + "defaultMessage": "You do not have permission to manage this assessment." }, - "course.plagiarism.PlagiarismIndex.assessments.lastSubmittedAt": { - "defaultMessage": "Last Submission At" + "course.plagiarism.PlagiarismIndex.assessments.AssessmentLinkList.noAssessmentsFound": { + "defaultMessage": "No assessments found" + }, + "course.plagiarism.PlagiarismIndex.assessments.actions": { + "defaultMessage": "Actions" + }, + "course.plagiarism.PlagiarismIndex.assessments.assessment": { + "defaultMessage": "Assessment" + }, + "course.plagiarism.PlagiarismIndex.assessments.confirmRerunMessage": { + "defaultMessage": "Some of the selected assessments already have completed plagiarism checks. Running a new plagiarism check will remove the previous results." + }, + "course.plagiarism.PlagiarismIndex.assessments.confirmRerunTitle": { + "defaultMessage": "Confirm Plagiarism Check?" }, "course.plagiarism.PlagiarismIndex.assessments.lastRunStatus": { "defaultMessage": "Status" @@ -5952,71 +6830,125 @@ "course.plagiarism.PlagiarismIndex.assessments.lastRunTime": { "defaultMessage": "Last Run At" }, - "course.plagiarism.PlagiarismIndex.assessments.statusNotStarted": { - "defaultMessage": "Not Started" + "course.plagiarism.PlagiarismIndex.assessments.lastSubmittedAt": { + "defaultMessage": "Last Submission At" }, - "course.plagiarism.PlagiarismIndex.assessments.statusRunning": { - "defaultMessage": "Running" + "course.plagiarism.PlagiarismIndex.assessments.linkAssessments": { + "defaultMessage": "Link Assessments" }, - "course.plagiarism.PlagiarismIndex.assessments.statusCompleted": { - "defaultMessage": "Completed" + "course.plagiarism.PlagiarismIndex.assessments.newSubmissionsWarning": { + "defaultMessage": "New submissions detected since last plagiarism run" }, - "course.plagiarism.PlagiarismIndex.assessments.statusFailed": { - "defaultMessage": "Failed" + "course.plagiarism.PlagiarismIndex.assessments.noNewSubmissionsWarning": { + "defaultMessage": "No new submissions since last plagiarism run" }, "course.plagiarism.PlagiarismIndex.assessments.noPlagiarismCheckableQuestions": { "defaultMessage": "No checkable questions" }, - "course.plagiarism.PlagiarismIndex.assessments.notEnoughSubmissions": { + "course.plagiarism.PlagiarismIndex.assessments.notEnoughSubmissions": { "defaultMessage": "Not enough submissions" }, + "course.plagiarism.PlagiarismIndex.assessments.numCheckableQuestions": { + "defaultMessage": "# Checkable Questions" + }, + "course.plagiarism.PlagiarismIndex.assessments.numSubmitted": { + "defaultMessage": "# Submissions" + }, "course.plagiarism.PlagiarismIndex.assessments.runAssessmentsPlagiarism": { "defaultMessage": "New Plagiarism Check ({count})" }, - "course.plagiarism.PlagiarismIndex.assessments.runPlagiarismCheckSuccess": { - "defaultMessage": "Started plagiarism check for {count, plural, =1 {# assessment} other {# assessments}}" + "course.plagiarism.PlagiarismIndex.assessments.runPlagiarismCheck": { + "defaultMessage": "Run Plagiarism Check" }, "course.plagiarism.PlagiarismIndex.assessments.runPlagiarismCheckError": { "defaultMessage": "Failed to start plagiarism checks for some assessments" }, + "course.plagiarism.PlagiarismIndex.assessments.runPlagiarismCheckSuccess": { + "defaultMessage": "Started plagiarism check for {count, plural, =1 {# assessment} other {# assessments}}" + }, "course.plagiarism.PlagiarismIndex.assessments.searchByAssessmentTitle": { "defaultMessage": "Search by Assessment Title" }, - "course.plagiarism.PlagiarismIndex.assessments.actions": { - "defaultMessage": "Actions" + "course.plagiarism.PlagiarismIndex.assessments.statusCompleted": { + "defaultMessage": "Completed" }, - "course.plagiarism.PlagiarismIndex.assessments.runPlagiarismCheck": { - "defaultMessage": "Run Plagiarism Check" + "course.plagiarism.PlagiarismIndex.assessments.statusFailed": { + "defaultMessage": "Failed" + }, + "course.plagiarism.PlagiarismIndex.assessments.statusNotStarted": { + "defaultMessage": "Not Started" + }, + "course.plagiarism.PlagiarismIndex.assessments.statusRunning": { + "defaultMessage": "Running" + }, + "course.plagiarism.PlagiarismIndex.assessments.statusStarting": { + "defaultMessage": "Starting" }, "course.plagiarism.PlagiarismIndex.assessments.viewResults": { "defaultMessage": "View Results" }, - "course.plagiarism.PlagiarismIndex.assessments.newSubmissionsWarning": { - "defaultMessage": "New submissions detected since last plagiarism run" + "course.plagiarism.PlagiarismIndex.header.plagiarism": { + "defaultMessage": "Plagiarism Check" }, - "course.plagiarism.PlagiarismIndex.assessments.noNewSubmissionsWarning": { - "defaultMessage": "No new submissions since last plagiarism run" + "course.statistics.StatisticsIndex.assessments.averageGrade": { + "defaultMessage": "Avg Grade" }, - "course.plagiarism.PlagiarismIndex.assessments.confirmRerunTitle": { - "defaultMessage": "Confirm Plagiarism Check?" + "course.statistics.StatisticsIndex.assessments.averageTimeTaken": { + "defaultMessage": "Avg Time" }, - "course.plagiarism.PlagiarismIndex.assessments.confirmRerunMessage": { - "defaultMessage": "Some of the selected assessments already have completed plagiarism checks. Running a new plagiarism check will remove the previous results." + "course.statistics.StatisticsIndex.assessments.category": { + "defaultMessage": "Category" }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.achievementCount": { - "defaultMessage": "No. of Achievements (Total: {courseAchievementCount})" + "course.statistics.StatisticsIndex.assessments.csvFileTitle": { + "defaultMessage": "Assessments Statistics" }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.ascending": { - "defaultMessage": "Ascending" + "course.statistics.StatisticsIndex.assessments.downloadCsv": { + "defaultMessage": "Download Score Summary for the following Assessments?" }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.correctness": { - "defaultMessage": "Correctness" + "course.statistics.StatisticsIndex.assessments.downloadScoreSummaryFailure": { + "defaultMessage": "An error occurred while downloading score summary" + }, + "course.statistics.StatisticsIndex.assessments.downloadScoreSummaryPending": { + "defaultMessage": "Please wait as your request to download is being processed" + }, + "course.statistics.StatisticsIndex.assessments.downloadScoreSummarySuccess": { + "defaultMessage": "Successfully downloaded score summary" + }, + "course.statistics.StatisticsIndex.assessments.numLateStudents": { + "defaultMessage": "# Late" + }, + "course.statistics.StatisticsIndex.assessments.numSubmittedStudents": { + "defaultMessage": "# Attempted" + }, + "course.statistics.StatisticsIndex.assessments.searchBar": { + "defaultMessage": "Search by Assessment Title, Tab, or Category" + }, + "course.statistics.StatisticsIndex.assessments.selectedNUsers": { + "defaultMessage": "Download Score Summary ({n, plural, =1 {# assessment} other {# assessments}})" + }, + "course.statistics.StatisticsIndex.assessments.startAt": { + "defaultMessage": "Starts At" + }, + "course.statistics.StatisticsIndex.assessments.stdevGrade": { + "defaultMessage": "Stdev Grade" }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.correctnessHint": { - "defaultMessage": "Correctness is the average grade percentage of all graded assessments by a student." + "course.statistics.StatisticsIndex.assessments.stdevTimeTaken": { + "defaultMessage": "Stdev Time" + }, + "course.statistics.StatisticsIndex.assessments.tab": { + "defaultMessage": "Tab" + }, + "course.statistics.StatisticsIndex.assessments.tableTitle": { + "defaultMessage": "Assessments Statistics ({numStudents} students)" }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.descending": { - "defaultMessage": "Descending" + "course.statistics.StatisticsIndex.assessments.title": { + "defaultMessage": "Title" + }, + "course.statistics.StatisticsIndex.course.StudentPerformanceTable.achievementCountDetails": { + "defaultMessage": "No. of Achievements (Total: {courseAchievementCount})" + }, + "course.statistics.StatisticsIndex.course.StudentPerformanceTable.correctness": { + "defaultMessage": "Correctness" }, "course.statistics.StatisticsIndex.course.StudentPerformanceTable.experiencePoints": { "defaultMessage": "Experience Points" @@ -6025,32 +6957,23 @@ "defaultMessage": "Tutors" }, "course.statistics.StatisticsIndex.course.StudentPerformanceTable.highlight": { - "defaultMessage": "Highlight top and bottom {percent}%" + "defaultMessage": "Highlight top and bottom {percent}% based on {criteria}" }, "course.statistics.StatisticsIndex.course.StudentPerformanceTable.learningRate": { "defaultMessage": "Learning Rate" }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.learningRateHint": { - "defaultMessage": "A learning rate of 200% means that they can complete the course in half the time." - }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.level": { + "course.statistics.StatisticsIndex.course.StudentPerformanceTable.levelInfo": { "defaultMessage": "Level (Max: {maxLevel})" }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.levelFilter": { - "defaultMessage": "Level: {name}" - }, "course.statistics.StatisticsIndex.course.StudentPerformanceTable.name": { "defaultMessage": "Name" }, "course.statistics.StatisticsIndex.course.StudentPerformanceTable.noData": { "defaultMessage": "No Data" }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.numSubmissions": { + "course.statistics.StatisticsIndex.course.StudentPerformanceTable.numSubmissionsDetails": { "defaultMessage": "No. of Submissions (Total: {courseAssessmentCount})" }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.phantom": { - "defaultMessage": "Include phantom users" - }, "course.statistics.StatisticsIndex.course.StudentPerformanceTable.studentType": { "defaultMessage": "Student Type" }, @@ -6060,27 +6983,15 @@ "course.statistics.StatisticsIndex.course.StudentPerformanceTable.studentType.phantom": { "defaultMessage": "Phantom" }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.tableTitle": { - "defaultMessage": "Students Sorted in {direction} {column}" - }, "course.statistics.StatisticsIndex.course.StudentPerformanceTable.title": { "defaultMessage": "Student Performance" }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.tutorFilter": { - "defaultMessage": "Tutor: {name}" - }, - "course.statistics.StatisticsIndex.course.StudentPerformanceTable.videoPercentWatched": { - "defaultMessage": "Video % Count" - }, "course.statistics.StatisticsIndex.course.StudentPerformanceTable.videoPercentWatchedHeader": { "defaultMessage": "Average Video % Watched" }, "course.statistics.StatisticsIndex.course.StudentPerformanceTable.videoSubmissionCountHeader": { "defaultMessage": "Videos Watched (Total: {courseVideoCount})" }, - "course.statistics.StatisticsIndex.course.searchBar": { - "defaultMessage": "Search by Student Name" - }, "course.statistics.StatisticsIndex.course.StudentProgressionChart.deadlines": { "defaultMessage": "Deadlines" }, @@ -6108,86 +7019,23 @@ "course.statistics.StatisticsIndex.course.StudentProgressionChart.xAxisLabel": { "defaultMessage": "Date" }, - "course.statistics.StatisticsIndex.course.StudentProgressionChart.yAxisLabel": { - "defaultMessage": "Assessment (Sorted by Deadline)" - }, - "course.statistics.StatisticsIndex.course.error": { - "defaultMessage": "Something went wrong when fetching course statistics! Please refresh to try again." - }, - "course.statistics.StatisticsIndex.course.performanceError": { - "defaultMessage": "Something went wrong when fetching course performance statistics! Please refresh to try again." - }, - "course.statistics.StatisticsIndex.course.progressionError": { - "defaultMessage": "Something went wrong when fetching course progression statistics! Please refresh to try again." - }, - "course.statistics.StatisticsIndex.header.statistics": { - "defaultMessage": "Statistics" - }, - "course.statistics.StatisticsIndex.assessments.title": { - "defaultMessage": "Title" - }, - "course.statistics.StatisticsIndex.assessments.startAt": { - "defaultMessage": "Starts At" - }, - "course.statistics.StatisticsIndex.assessments.tab": { - "defaultMessage": "Tab" - }, - "course.statistics.StatisticsIndex.assessments.category": { - "defaultMessage": "Category" - }, - "course.statistics.StatisticsIndex.assessments.numSubmittedStudents": { - "defaultMessage": "# Submitted" - }, - "course.statistics.StatisticsIndex.assessments.numAttemptedStudents": { - "defaultMessage": "# Attempted" - }, - "course.statistics.StatisticsIndex.assessments.numLateStudents": { - "defaultMessage": "# Late" - }, - "course.statistics.StatisticsIndex.assessments.averageGrade": { - "defaultMessage": "Avg Grade" - }, - "course.statistics.StatisticsIndex.assessments.stdevGrade": { - "defaultMessage": "Stdev Grade" - }, - "course.statistics.StatisticsIndex.assessments.averageTimeTaken": { - "defaultMessage": "Avg Time" - }, - "course.statistics.StatisticsIndex.assessments.stdevTimeTaken": { - "defaultMessage": "Stdev Time" - }, - "course.statistics.StatisticsIndex.assessments.tableTitle": { - "defaultMessage": "Assessments Statistics ({numStudents} students)" - }, - "course.statistics.StatisticsIndex.assessments.csvFileTitle": { - "defaultMessage": "Assessments Statistics" - }, - "course.statistics.StatisticsIndex.assessments.searchBar": { - "defaultMessage": "Search by Assessment Title, Tab, or Category" - }, - "course.statistics.StatisticsIndex.assessments.selectedNUsers": { - "defaultMessage": "Download Score Summary for {numUsers} students?" - }, - "course.statistics.StatisticsIndex.assessments.downloadCsv": { - "defaultMessage": "Download" - }, - "course.statistics.StatisticsIndex.assessments.downloadScoreSummary": { - "defaultMessage": "Download Score Summary for the following Assessments?" + "course.statistics.StatisticsIndex.course.StudentProgressionChart.yAxisLabel": { + "defaultMessage": "Assessment (Sorted by Deadline)" }, - "course.statistics.StatisticsIndex.assessments.downloadScoreSummarySuccess": { - "defaultMessage": "Successfully downloaded score summary" + "course.statistics.StatisticsIndex.course.csvFileTitle": { + "defaultMessage": "Student Performance Statistics" }, - "course.statistics.StatisticsIndex.assessments.downloadScoreSummaryFailure": { - "defaultMessage": "An error occurred while downloading score summary" + "course.statistics.StatisticsIndex.course.searchBar": { + "defaultMessage": "Search by Student Name" }, - "course.statistics.StatisticsIndex.assessments.downloadScoreSummaryPending": { - "defaultMessage": "Your download is being processed. Please wait." + "course.statistics.StatisticsIndex.header.statistics": { + "defaultMessage": "Statistics" }, "course.statistics.StatisticsIndex.staff.averageMarkingTime": { "defaultMessage": "Avg Time / Assessment" }, - "course.statistics.StatisticsIndex.staff.error": { - "defaultMessage": "Something went wrong when fetching staff statistics! Please refresh to try again." + "course.statistics.StatisticsIndex.staff.csvFileTitle": { + "defaultMessage": "Staff Statistics" }, "course.statistics.StatisticsIndex.staff.name": { "defaultMessage": "Name" @@ -6198,23 +7046,20 @@ "course.statistics.StatisticsIndex.staff.numStudents": { "defaultMessage": "# Students" }, + "course.statistics.StatisticsIndex.staff.searchBar": { + "defaultMessage": "Search by Staff Name" + }, "course.statistics.StatisticsIndex.staff.stddev": { - "defaultMessage": "Standard Deviation" + "defaultMessage": "Stdev Time / Assessment" }, "course.statistics.StatisticsIndex.staff.tableTitle": { "defaultMessage": "Staff Statistics" }, - "course.statistics.StatisticsIndex.staff.csvFileTitle": { - "defaultMessage": "Staff Statistics" - }, - "course.statistics.StatisticsIndex.staff.searchBar": { - "defaultMessage": "Search by Staff Name" - }, - "course.statistics.StatisticsIndex.staffFailure": { - "defaultMessage": "Failed to fetch staff data!" + "course.statistics.StatisticsIndex.students.csvFileTitle": { + "defaultMessage": "Student Statistics" }, - "course.statistics.StatisticsIndex.students.error": { - "defaultMessage": "Something went wrong when fetching student statistics! Please refresh to try again." + "course.statistics.StatisticsIndex.students.email": { + "defaultMessage": "Email" }, "course.statistics.StatisticsIndex.students.experiencePoints": { "defaultMessage": "Experience Points" @@ -6228,14 +7073,8 @@ "course.statistics.StatisticsIndex.students.name": { "defaultMessage": "Name" }, - "course.statistics.StatisticsIndex.students.email": { - "defaultMessage": "Email" - }, - "course.statistics.StatisticsIndex.students.noStudents": { - "defaultMessage": "There is no student in this course, yet..." - }, - "course.statistics.StatisticsIndex.students.showMyStudentsOnly": { - "defaultMessage": "Show My Students Only" + "course.statistics.StatisticsIndex.students.searchBar": { + "defaultMessage": "Search by Student Name or Student Type" }, "course.statistics.StatisticsIndex.students.studentsType": { "defaultMessage": "Student Type" @@ -6243,21 +7082,12 @@ "course.statistics.StatisticsIndex.students.tableTitle": { "defaultMessage": "Student Statistics ({numStudents} students, {numPhantom} phantom)" }, - "course.statistics.StatisticsIndex.students.tutorFilter": { - "defaultMessage": "Tutor: {name}" - }, "course.statistics.StatisticsIndex.students.videoPercentWatched": { "defaultMessage": "Average % Watched" }, "course.statistics.StatisticsIndex.students.videoSubmissionCount": { "defaultMessage": "Videos Watched (Total: {courseVideoCount})" }, - "course.statistics.StatisticsIndex.students.csvFileTitle": { - "defaultMessage": "Student Statistics" - }, - "course.statistics.StatisticsIndex.students.searchBar": { - "defaultMessage": "Search by Student Name or Student Type" - }, "course.statistics.StatisticsIndex.studentsFailure": { "defaultMessage": "Failed to fetch student data!" }, @@ -6282,12 +7112,6 @@ "course.statistics.course.studentProgressionChart.startAt": { "defaultMessage": "Starts at: {startAt}" }, - "course.statistics.failures.coursePerformance": { - "defaultMessage": "Failed to fetch course performance data!" - }, - "course.statistics.failures.courseProgression": { - "defaultMessage": "Failed to fetch course progression data!" - }, "course.statistics.tabs.course": { "defaultMessage": "Course" }, @@ -6297,6 +7121,15 @@ "course.statistics.tabs.courseProgression": { "defaultMessage": "Course Progression" }, + "course.stories.CikgoErrorPage.errorFetching": { + "defaultMessage": "Either it's supposed to be naught, or something went wrong." + }, + "course.stories.CikgoErrorPage.errorFetchingDescription": { + "defaultMessage": "Cikgo is our partner that powers this experience. They were contactable, but did not give us any resources for this request just now. Please try again later, and if this persists, contact us." + }, + "course.stories.pages.MissionControlPage": { + "defaultMessage": "Mission Control" + }, "course.survey.DeleteSectionButton.deleteSection": { "defaultMessage": "Delete Section" }, @@ -6784,7 +7617,7 @@ "defaultMessage": "Revert and delete timeline and its times" }, "course.timelines.defaultTimeline": { - "defaultMessage": "Default" + "defaultMessage": "Default Timeline" }, "course.timelines.deleteTime": { "defaultMessage": "Delete time" @@ -6942,6 +7775,9 @@ "course.userInvitation.InviteUsersRegistrationCode.registrationCodeNote": { "defaultMessage": "Users who have been invited and use this invitation code to register for the course would not have the proper status reflected in the Invitations page." }, + "course.userInvitations.IndividualInvitations.addRowsByEmail": { + "defaultMessage": "Add Rows by Email" + }, "course.userInvitations.IndividualInvitations.appendNewRow": { "defaultMessage": "Add Row" }, @@ -6951,12 +7787,39 @@ "course.userInvitations.IndividualInvitations.invite": { "defaultMessage": "Invite All Users" }, + "course.userInvitations.IndividualInvitations.malformedEmail": { + "defaultMessage": "{n, plural, one {This email is } other {These emails are }} wrongly formatted: {emails}" + }, + "course.userInvitations.IndividualInvitations.nameEmailInput": { + "defaultMessage": "John Doe '; \"Doe, Jane\" '; ..." + }, "course.userInvitations.IndividualInvitations.namePlaceholder": { "defaultMessage": "Awesome User" }, "course.userInvitations.IndividualInvitations.removeInvitation": { "defaultMessage": "Remove Invitation" }, + "course.userInvitations.InvitationActionButtons.deletionConfirm": { + "defaultMessage": "Are you sure you wish to delete invitation to {name} ({email})?" + }, + "course.userInvitations.InvitationActionButtons.deletionFailure": { + "defaultMessage": "Failed to delete user - {error}" + }, + "course.userInvitations.InvitationActionButtons.deletionSuccess": { + "defaultMessage": "Invitation for {name} was deleted." + }, + "course.userInvitations.InvitationActionButtons.deletionTooltip": { + "defaultMessage": "Delete Invitation" + }, + "course.userInvitations.InvitationActionButtons.resendFailure": { + "defaultMessage": "Failed to resend invitation - {error}" + }, + "course.userInvitations.InvitationActionButtons.resendSuccess": { + "defaultMessage": "Resent email invitation to {email}!" + }, + "course.userInvitations.InvitationActionButtons.resendTooltip": { + "defaultMessage": "Resend Invitation" + }, "course.userInvitations.InvitationResultDialog.body": { "defaultMessage": "{newInvitationsCount, plural, =0 {No new users were} one {# new user has been} other {# new users have been}} invited to Coursemology. {newCourseUsersCount, plural, =0 {No user with Coursemology account has been} one {# new user with existing Coursemology account has been} other {# new users with existing Coursemology accounts have been}} added to this course." }, @@ -6990,9 +7853,6 @@ "course.userInvitations.InvitationResultDialog.newInvitations": { "defaultMessage": "New Invitations ({count})" }, - "course.userInvitations.InvitationsBarChart.accepted": { - "defaultMessage": "Accepted Invitations" - }, "course.userInvitations.InvitationsIndex.failure": { "defaultMessage": "Failed to fetch all invitations" }, @@ -7050,27 +7910,6 @@ "course.userInvitations.InviteUsersfileUploadForm.invite": { "defaultMessage": "Invite Users from File" }, - "course.userInvitations.InvitationActionButtons.deletionConfirm": { - "defaultMessage": "Are you sure you wish to delete invitation to {name} ({email})?" - }, - "course.userInvitations.InvitationActionButtons.deletionFailure": { - "defaultMessage": "Failed to delete user - {error}" - }, - "course.userInvitations.InvitationActionButtons.deletionSuccess": { - "defaultMessage": "Invitation for {name} was deleted." - }, - "course.userInvitations.InvitationActionButtons.deletionTooltip": { - "defaultMessage": "Delete Invitation" - }, - "course.userInvitations.InvitationActionButtons.resendFailure": { - "defaultMessage": "Failed to resend invitation - {error}" - }, - "course.userInvitations.InvitationActionButtons.resendSuccess": { - "defaultMessage": "Resent email invitation to {email}!" - }, - "course.userInvitations.InvitationActionButtons.resendTooltip": { - "defaultMessage": "Resend Invitation" - }, "course.userInvitations.RegistrationCodeButton.registrationCode": { "defaultMessage": "Registration Code" }, @@ -7089,6 +7928,9 @@ "course.userInvitations.UserInvitationsTable.accepted": { "defaultMessage": "Accepted" }, + "course.userInvitations.UserInvitationsTable.confirmedTooltip": { + "defaultMessage": "Accepted {confirmedAt}" + }, "course.userInvitations.UserInvitationsTable.failed": { "defaultMessage": "Failed" }, @@ -7101,9 +7943,6 @@ "course.userInvitations.UserInvitationsTable.sentTooltip": { "defaultMessage": "Sent {sentAt}" }, - "course.userInvitations.UserInvitationsTable.confirmedTooltip": { - "defaultMessage": "Accepted {confirmedAt}" - }, "course.userNotification.AchievementGainedPopup.unlocked": { "defaultMessage": "Achievement Unlocked!" }, @@ -7122,12 +7961,6 @@ "course.users.ExperiencePointsRecords.experiencePointsHistoryHeader": { "defaultMessage": "Experience Points History: {for}" }, - "course.users.ExperiencePointsRecords.fetchUsersFailure": { - "defaultMessage": "Failed to fetch records" - }, - "course.users.ExperiencePointsTable.fetchRecordsFailure": { - "defaultMessage": "Failed to fetch records" - }, "course.users.ManageStaff.noStaff": { "defaultMessage": "No staff in course." }, @@ -7185,6 +8018,15 @@ "course.users.ManageUsersTable.defaultTimeline": { "defaultMessage": "Default" }, + "course.users.ManageUsersTable.deletionConfirm": { + "defaultMessage": "Are you sure you wish to delete {role} {name} ({email})?" + }, + "course.users.ManageUsersTable.deletionFailure": { + "defaultMessage": "Failed to delete {role} {name} ({email})." + }, + "course.users.ManageUsersTable.deletionScheduled": { + "defaultMessage": "{role} {name} ({email}) has been scheduled for deletion." + }, "course.users.ManageUsersTable.group": { "defaultMessage": "Group: {name}" }, @@ -7200,6 +8042,24 @@ "course.users.ManageUsersTable.selectedNUsers": { "defaultMessage": "Selected {n, plural, =1 {# user} other {# users}}" }, + "course.users.ManageUsersTable.suspend": { + "defaultMessage": "Suspend" + }, + "course.users.ManageUsersTable.suspendFailure": { + "defaultMessage": "Failed to suspend {name}." + }, + "course.users.ManageUsersTable.suspendSuccess": { + "defaultMessage": "{name} is now suspended. They cannot access this course until they are unsuspended." + }, + "course.users.ManageUsersTable.unsuspend": { + "defaultMessage": "Unsuspend" + }, + "course.users.ManageUsersTable.unsuspendFailure": { + "defaultMessage": "Failed to unsuspend {name}." + }, + "course.users.ManageUsersTable.unsuspendSuccess": { + "defaultMessage": "{name} is no longer suspended. They can now access the course." + }, "course.users.ManageUsersTable.updateFailure": { "defaultMessage": "Failed to update user - {error}" }, @@ -7308,36 +8168,6 @@ "course.users.UpgradeToStaff.upgradeSuccess": { "defaultMessage": "{count, plural, =0 {No users were} one {# new user has} other {# new users have}} been upgraded to {role}" }, - "course.users.ManageUsersTable.deletionConfirm": { - "defaultMessage": "Are you sure you wish to delete {role} {name} ({email})?" - }, - "course.users.ManageUsersTable.deletionFailure": { - "defaultMessage": "Failed to delete user." - }, - "course.users.ManageUsersTable.deletionScheduled": { - "defaultMessage": "{role} {name} ({email}) has been scheduled for deletion." - }, - "course.users.ManageUsersTable.deletionSuccess": { - "defaultMessage": "User was deleted." - }, - "course.users.ManageUsersTable.suspend": { - "defaultMessage": "Suspend" - }, - "course.users.ManageUsersTable.suspendFailure": { - "defaultMessage": "Failed to suspend {name}." - }, - "course.users.ManageUsersTable.suspendSuccess": { - "defaultMessage": "{name} is now suspended. They cannot access this course until they are unsuspended." - }, - "course.users.ManageUsersTable.unsuspend": { - "defaultMessage": "Unsuspend" - }, - "course.users.ManageUsersTable.unsuspendFailure": { - "defaultMessage": "Failed to unsuspend {name}." - }, - "course.users.ManageUsersTable.unsuspendSuccess": { - "defaultMessage": "{name} is no longer suspended. They can now access the course." - }, "course.users.UserManagementTabs.enrolRequestsTitle": { "defaultMessage": "Enrol Requests" }, @@ -7476,27 +8306,27 @@ "course.video.VideoShow.videoTitle": { "defaultMessage": "Video - {title}" }, + "course.video.VideoTable.actions": { + "defaultMessage": "Actions" + }, + "course.video.VideoTable.averageWatched": { + "defaultMessage": "Average % Watched" + }, "course.video.VideoTable.noVideo": { "defaultMessage": "No Video" }, - "course.video.VideoTable.title": { - "defaultMessage": "Title" + "course.video.VideoTable.published": { + "defaultMessage": "Published" }, "course.video.VideoTable.startAt": { "defaultMessage": "Start At" }, + "course.video.VideoTable.title": { + "defaultMessage": "Title" + }, "course.video.VideoTable.watchCount": { "defaultMessage": "Watch Count" }, - "course.video.VideoTable.averageWatched": { - "defaultMessage": "Average % Watched" - }, - "course.video.VideoTable.published": { - "defaultMessage": "Published" - }, - "course.video.VideoTable.actions": { - "defaultMessage": "Actions" - }, "course.video.VideosIndex.fetchVideosFailure": { "defaultMessage": "Failed to retrieve videos." }, @@ -7644,9 +8474,33 @@ "course.videoSubmissions.UserVideoSubmissionsIndex.videoSubmissionsHeader": { "defaultMessage": "Video Watch History" }, + "d6avGo": { + "defaultMessage": "Submissions" + }, + "f9aTl7": { + "defaultMessage": "Role-Playing Assessments" + }, + "jvrMfo": { + "defaultMessage": "Assistants" + }, "landing_page.create_an_account": { "defaultMessage": "Create an account" }, + "landing_page.iconEngaging": { + "defaultMessage": "Engaging" + }, + "landing_page.iconEngagingSubtitle": { + "defaultMessage": "It is built for all teachers. You do not need to have any programming knowledge to master the platform. Coursemology is easy and intuitive to use for both teachers and students." + }, + "landing_page.iconGeneral": { + "defaultMessage": "General" + }, + "landing_page.iconGeneralSubtitle": { + "defaultMessage": "It is built for all subjects. The gamification system of Coursemology does not make any assumptions on the subject. Through Coursemology, any teacher who teaches any subject can turn his course exercises into an online game." + }, + "landing_page.iconSimple": { + "defaultMessage": "Simple" + }, "landing_page.new_to_coursemology": { "defaultMessage": "New to Coursemology?" }, @@ -7683,12 +8537,12 @@ "lib.components.core.Expandable.showMore": { "defaultMessage": "Show more" }, - "lib.components.core.Note.noteHeader": { - "defaultMessage": "Note" - }, "lib.components.core.Note.errorHeader": { "defaultMessage": "Error" }, + "lib.components.core.Note.noteHeader": { + "defaultMessage": "Note" + }, "lib.components.core.banners.ServerUnreachableBanner.refreshPage": { "defaultMessage": "Refresh page" }, @@ -7818,6 +8672,69 @@ "lib.components.form.fields.SingleFileInput.removeFile": { "defaultMessage": "Remove File" }, + "lib.components.getHelp.filter.filterAssessmentLabel": { + "defaultMessage": "Filter by Assessment" + }, + "lib.components.getHelp.filter.filterCourseLabel": { + "defaultMessage": "Filter by Course" + }, + "lib.components.getHelp.filter.filterEndDateLabel": { + "defaultMessage": "End Date" + }, + "lib.components.getHelp.filter.filterStartDateLabel": { + "defaultMessage": "Start Date" + }, + "lib.components.getHelp.filter.filterStudentLabel": { + "defaultMessage": "Filter by Student" + }, + "lib.components.getHelp.filter.lastFourteenDays": { + "defaultMessage": "Last 14 Days" + }, + "lib.components.getHelp.filter.lastSevenDays": { + "defaultMessage": "Last 7 Days" + }, + "lib.components.getHelp.filter.lastSixMonths": { + "defaultMessage": "Last 6 Months" + }, + "lib.components.getHelp.filter.lastThirtyDays": { + "defaultMessage": "Last 30 Days" + }, + "lib.components.getHelp.filter.lastTwelveMonths": { + "defaultMessage": "Last 12 Months" + }, + "lib.components.getHelp.header": { + "defaultMessage": "Recent Get Help Activity ({total, plural, one {# Conversation} other {# Conversations}})" + }, + "lib.components.getHelp.table.assessmentTitle": { + "defaultMessage": "Assessment" + }, + "lib.components.getHelp.table.courseTitle": { + "defaultMessage": "Course" + }, + "lib.components.getHelp.table.createdAt": { + "defaultMessage": "Last Message At" + }, + "lib.components.getHelp.table.instanceTitle": { + "defaultMessage": "Instance" + }, + "lib.components.getHelp.table.lastMessage": { + "defaultMessage": "Last Message" + }, + "lib.components.getHelp.table.messageCount": { + "defaultMessage": "# Msgs" + }, + "lib.components.getHelp.table.questionNumber": { + "defaultMessage": "Question" + }, + "lib.components.getHelp.table.studentName": { + "defaultMessage": "Name" + }, + "lib.components.getHelp.validation.exceedDateRange": { + "defaultMessage": "Date range cannot exceed 365 days" + }, + "lib.components.getHelp.validation.invalidDateSelection": { + "defaultMessage": "End Date must be after or equal to Start Date" + }, "lib.components.navigation.AdminPopupMenuList.adminPanel": { "defaultMessage": "System Admin Panel" }, @@ -7854,6 +8771,24 @@ "lib.components.navigation.CourseSwitcherPopupMenu.thisCourse": { "defaultMessage": "This course" }, + "lib.components.table.MuiColumnPickerDialog.apply": { + "defaultMessage": "Apply to view" + }, + "lib.components.table.MuiColumnPickerDialog.cancel": { + "defaultMessage": "Cancel" + }, + "lib.components.table.MuiColumnPickerDialog.defaultTitle": { + "defaultMessage": "Select columns" + }, + "lib.components.table.MuiColumnPickerDialog.export": { + "defaultMessage": "Apply and Export" + }, + "lib.components.table.MuiTableToolbar.directExport": { + "defaultMessage": "Export" + }, + "lib.components.table.MuiTableToolbar.exportTrigger": { + "defaultMessage": "Export…" + }, "lib.hooks.router.usePrompt.sureYouWantToLeave": { "defaultMessage": "Are you sure you want to leave this page? You will lose unsaved changes." }, @@ -7881,108 +8816,27 @@ "lib.translations.beta": { "defaultMessage": "Beta" }, - "lib.components.getHelp.header": { - "defaultMessage": "Recent Get Help Activity ({total, plural, one {# Conversation} other {# Conversations}})" - }, - "lib.components.getHelp.filter.filterCourseLabel": { - "defaultMessage": "Filter by Course" - }, - "lib.components.getHelp.filter.filterAssessmentLabel": { - "defaultMessage": "Filter by Assessment" - }, - "lib.components.getHelp.filter.filterStudentLabel": { - "defaultMessage": "Filter by Student" - }, - "lib.components.getHelp.filter.filterStartDateLabel": { - "defaultMessage": "Start Date" - }, - "lib.components.getHelp.filter.filterEndDateLabel": { - "defaultMessage": "End Date" - }, - "lib.components.getHelp.filter.lastSevenDays": { - "defaultMessage": "Last 7 Days" - }, - "lib.components.getHelp.filter.lastFourteenDays": { - "defaultMessage": "Last 14 Days" - }, - "lib.components.getHelp.filter.lastThirtyDays": { - "defaultMessage": "Last 30 Days" - }, - "lib.components.getHelp.filter.lastSixMonths": { - "defaultMessage": "Last 6 Months" - }, - "lib.components.getHelp.filter.lastTwelveMonths": { - "defaultMessage": "Last 12 Months" - }, - "lib.components.getHelp.table.studentName": { - "defaultMessage": "Name" - }, - "lib.components.getHelp.table.messageCount": { - "defaultMessage": "# Msgs" - }, - "lib.components.getHelp.table.lastMessage": { - "defaultMessage": "Last Message" - }, - "lib.components.getHelp.table.questionNumber": { - "defaultMessage": "Question" - }, - "lib.components.getHelp.table.assessmentTitle": { - "defaultMessage": "Assessment" - }, - "lib.components.getHelp.table.createdAt": { - "defaultMessage": "Last Message At" - }, - "lib.components.getHelp.table.courseTitle": { - "defaultMessage": "Course" - }, - "lib.components.getHelp.table.instanceTitle": { - "defaultMessage": "Instance" - }, - "lib.components.getHelp.validation.invalidDateSelection": { - "defaultMessage": "End Date must be after or equal to Start Date" - }, - "lib.components.getHelp.validation.exceedDateRange": { - "defaultMessage": "Date range cannot exceed 365 days" - }, - "lib.components.table.MuiColumnPickerDialog.apply": { - "defaultMessage": "Apply to view" - }, - "lib.components.table.MuiColumnPickerDialog.cancel": { - "defaultMessage": "Cancel" - }, - "lib.components.table.MuiColumnPickerDialog.defaultTitle": { - "defaultMessage": "Select columns" - }, - "lib.components.table.MuiColumnPickerDialog.export": { - "defaultMessage": "Apply and Export" - }, - "lib.components.table.MuiTableToolbar.directExport": { - "defaultMessage": "Export" - }, - "lib.components.table.MuiTableToolbar.exportTrigger": { - "defaultMessage": "Export…" - }, "lib.translations.course.users.fetchUsersFailure": { "defaultMessage": "Failed to fetch users." }, "lib.translations.course.users.manageUsersHeader": { "defaultMessage": "Manage Users" }, - "lib.translations.course.users.roles.student": { - "defaultMessage": "Student" - }, - "lib.translations.course.users.roles.teachingAssistant": { - "defaultMessage": "Teaching Assistant" + "lib.translations.course.users.roles.manager": { + "defaultMessage": "Manager" }, "lib.translations.course.users.roles.observer": { "defaultMessage": "Observer" }, - "lib.translations.course.users.roles.manager": { - "defaultMessage": "Manager" - }, "lib.translations.course.users.roles.owner": { "defaultMessage": "Owner" }, + "lib.translations.course.users.roles.student": { + "defaultMessage": "Student" + }, + "lib.translations.course.users.roles.teachingAssistant": { + "defaultMessage": "Teaching Assistant" + }, "lib.translations.experimental": { "defaultMessage": "Experimental" }, @@ -8094,14 +8948,17 @@ "lib.translations.form.validation.startEndDateValidationError": { "defaultMessage": "Must be after Start Date" }, - "lib.translations.instance.users.roles.normal": { - "defaultMessage": "Normal" + "lib.translations.instance.users.roles.administrator": { + "defaultMessage": "Administrator" }, "lib.translations.instance.users.roles.instructor": { "defaultMessage": "Instructor" }, - "lib.translations.instance.users.roles.administrator": { - "defaultMessage": "Administrator" + "lib.translations.instance.users.roles.normal": { + "defaultMessage": "Normal" + }, + "lib.translations.messages.featureUnavailable": { + "defaultMessage": "This feature is currently unavailable." }, "lib.translations.messages.fetchingError": { "defaultMessage": "An error occurred when loading your data. Please reload and try again." @@ -8112,9 +8969,27 @@ "lib.translations.messages.loadImageError": { "defaultMessage": "An error occurred when loading your image. Please try selecting another one." }, + "lib.translations.myStudents": { + "defaultMessage": "My Students" + }, + "lib.translations.myStudentsIncludingPhantoms": { + "defaultMessage": "My Students (Including Phantoms)" + }, "lib.translations.no": { "defaultMessage": "No" }, + "lib.translations.staff": { + "defaultMessage": "Staff" + }, + "lib.translations.staffIncludingPhantoms": { + "defaultMessage": "Staff (Including Phantoms)" + }, + "lib.translations.students": { + "defaultMessage": "Students" + }, + "lib.translations.studentsIncludingPhantoms": { + "defaultMessage": "Students (Including Phantoms)" + }, "lib.translations.summary": { "defaultMessage": "Summary" }, @@ -8280,33 +9155,15 @@ "lib.translations.yes": { "defaultMessage": "Yes" }, - "material.attemptLoader.errorAccessingMaterial": { - "defaultMessage": "An error occurred while accessing this material. Try again later." - }, - "system.admin.instance.instance.InstanceAdminNavigator.announcements": { - "defaultMessage": "Announcements" - }, - "system.admin.instance.instance.InstanceAdminNavigator.components": { - "defaultMessage": "Components" - }, - "system.admin.instance.instance.InstanceAdminNavigator.courses": { - "defaultMessage": "Courses" - }, - "system.admin.instance.instance.InstanceAdminNavigator.roleRequests": { - "defaultMessage": "Role Requests" - }, - "system.admin.instance.instance.InstanceAdminNavigator.users": { - "defaultMessage": "Users" - }, - "system.admin.instance.instance.InstanceAdminNavigator.getHelp": { - "defaultMessage": "Get Help" - }, "system.admin.admin.AdminNavigator.announcements": { "defaultMessage": "System Announcements" }, "system.admin.admin.AdminNavigator.courses": { "defaultMessage": "Courses" }, + "system.admin.admin.AdminNavigator.getHelp": { + "defaultMessage": "Get Help" + }, "system.admin.admin.AdminNavigator.instances": { "defaultMessage": "Instances" }, @@ -8316,9 +9173,6 @@ "system.admin.admin.AdminNavigator.users": { "defaultMessage": "Users" }, - "system.admin.admin.AdminNavigator.getHelp": { - "defaultMessage": "Get Help" - }, "system.admin.admin.AnnouncementsIndex.fetchAnnouncementsFailure": { "defaultMessage": "Unable to fetch announcements" }, @@ -8403,21 +9257,24 @@ "system.admin.admin.InstancesTable.updateSuccess": { "defaultMessage": "Renamed {field} from {prevValue} to {newValue}" }, + "system.admin.admin.UsersButton.associatedCourses": { + "defaultMessage": "{courseName} ({instanceName})" + }, "system.admin.admin.UsersButton.deleteTooltip": { "defaultMessage": "Delete User" }, "system.admin.admin.UsersButton.deletionConfirm": { - "defaultMessage": "Are you sure you wish to delete {role} {name} ({email})?" + "defaultMessage": "Are you sure you wish to proceed?" }, "system.admin.admin.UsersButton.deletionConfirmTitle": { "defaultMessage": "Deleting {role} User {name} ({email})" }, - "system.admin.admin.UsersButton.deletionPromptContent": { - "defaultMessage": "Deleting this user will PERMANENTLY delete associated data in the following {count, plural, one {course} other {courses}}:" - }, "system.admin.admin.UsersButton.deletionFailure": { "defaultMessage": "Failed to delete user - {error}" }, + "system.admin.admin.UsersButton.deletionPromptContent": { + "defaultMessage": "Deleting this user will PERMANENTLY delete associated data in the following {count, plural, one {course} other {courses}}:" + }, "system.admin.admin.UsersButton.deletionSuccess": { "defaultMessage": "User was deleted." }, @@ -8463,6 +9320,24 @@ "system.admin.instance.instance.IndividualInvitations.invite": { "defaultMessage": "Invite All Users" }, + "system.admin.instance.instance.InstanceAdminNavigator.announcements": { + "defaultMessage": "Announcements" + }, + "system.admin.instance.instance.InstanceAdminNavigator.components": { + "defaultMessage": "Components" + }, + "system.admin.instance.instance.InstanceAdminNavigator.courses": { + "defaultMessage": "Courses" + }, + "system.admin.instance.instance.InstanceAdminNavigator.getHelp": { + "defaultMessage": "Get Help" + }, + "system.admin.instance.instance.InstanceAdminNavigator.roleRequests": { + "defaultMessage": "Role Requests" + }, + "system.admin.instance.instance.InstanceAdminNavigator.users": { + "defaultMessage": "Users" + }, "system.admin.instance.instance.InstanceAnnouncementsIndex.fetchAnnouncementsFailure": { "defaultMessage": "Unable to fetch announcements" }, @@ -8556,21 +9431,12 @@ "system.admin.instance.instance.InstanceUsersIndex.totalUsers": { "defaultMessage": "Total Users: {allCount} ({adminCount} Administrators, {instructorCount} Instructors, {normalCount} Normal)" }, - "system.admin.instance.instance.InstanceUsersInvitations.accepted": { - "defaultMessage": "Accepted Invitations" - }, - "system.admin.instance.instance.InstanceUsersInvitations.failed": { - "defaultMessage": "Failed Invitations" - }, "system.admin.instance.instance.InstanceUsersInvitations.fetch.failure": { "defaultMessage": "Failed to fetch invitations." }, "system.admin.instance.instance.InstanceUsersInvitations.header": { "defaultMessage": "Invitations" }, - "system.admin.instance.instance.InstanceUsersInvitations.pending": { - "defaultMessage": "Pending Invitations" - }, "system.admin.instance.instance.InstanceUsersInvitations.title": { "defaultMessage": "Users" }, @@ -8583,6 +9449,27 @@ "system.admin.instance.instance.InstanceUsersTabs.usersTab": { "defaultMessage": "Users" }, + "system.admin.instance.instance.InvitationActionButtons.deletionConfirm": { + "defaultMessage": "Are you sure you wish to delete invitation to {name} ({email})?" + }, + "system.admin.instance.instance.InvitationActionButtons.deletionFailure": { + "defaultMessage": "Failed to delete user - {error}" + }, + "system.admin.instance.instance.InvitationActionButtons.deletionSuccess": { + "defaultMessage": "Invitation for {name} was deleted." + }, + "system.admin.instance.instance.InvitationActionButtons.deletionTooltip": { + "defaultMessage": "Delete Invitation" + }, + "system.admin.instance.instance.InvitationActionButtons.resendFailure": { + "defaultMessage": "Failed to resend invitation - {error}" + }, + "system.admin.instance.instance.InvitationActionButtons.resendSuccess": { + "defaultMessage": "Resent email invitation to {email}!" + }, + "system.admin.instance.instance.InvitationActionButtons.resendTooltip": { + "defaultMessage": "Resend Invitation" + }, "system.admin.instance.instance.InvitationResultDialog.close": { "defaultMessage": "Close" }, @@ -8613,27 +9500,6 @@ "system.admin.instance.instance.InvitationResultDialog.newInvitations": { "defaultMessage": "New Invitations ({count})" }, - "system.admin.instance.instance.InvitationActionButtons.deletionConfirm": { - "defaultMessage": "Are you sure you wish to delete invitation to {name} ({email})?" - }, - "system.admin.instance.instance.InvitationActionButtons.deletionFailure": { - "defaultMessage": "Failed to delete user - {error}" - }, - "system.admin.instance.instance.InvitationActionButtons.deletionSuccess": { - "defaultMessage": "Invitation for {name} was deleted." - }, - "system.admin.instance.instance.InvitationActionButtons.deletionTooltip": { - "defaultMessage": "Delete Invitation" - }, - "system.admin.instance.instance.InvitationActionButtons.resendFailure": { - "defaultMessage": "Failed to resend invitation - {error}" - }, - "system.admin.instance.instance.InvitationActionButtons.resendSuccess": { - "defaultMessage": "Resent email invitation to {email}!" - }, - "system.admin.instance.instance.InvitationActionButtons.resendTooltip": { - "defaultMessage": "Resend Invitation" - }, "system.admin.instance.instance.PendingRoleRequestsButton.approveFailure": { "defaultMessage": "Failed to approve role request - {error}" }, @@ -8679,6 +9545,9 @@ "system.admin.instance.instance.UserInvitationsTable.accepted": { "defaultMessage": "Accepted" }, + "system.admin.instance.instance.UserInvitationsTable.confirmedTooltip": { + "defaultMessage": "Accepted {confirmedAt}" + }, "system.admin.instance.instance.UserInvitationsTable.failed": { "defaultMessage": "Failed" }, @@ -8691,9 +9560,6 @@ "system.admin.instance.instance.UserInvitationsTable.sentTooltip": { "defaultMessage": "Sent {sentAt}" }, - "system.admin.instance.instance.UserInvitationsTable.confirmedTooltip": { - "defaultMessage": "Accepted {confirmedAt}" - }, "system.admin.instance.instance.UsersButton.deleteTooltip": { "defaultMessage": "Remove User" }, @@ -8703,12 +9569,12 @@ "system.admin.instance.instance.UsersButton.deletionConfirmTitle": { "defaultMessage": "Removing {role} User {name} ({email})" }, - "system.admin.instance.instance.UsersButton.deletionPromptContent": { - "defaultMessage": "Removing this user may cause errors in the following {count, plural, one {course} other {courses}}:" - }, "system.admin.instance.instance.UsersButton.deletionFailure": { "defaultMessage": "Failed to remove user - {error}" }, + "system.admin.instance.instance.UsersButton.deletionPromptContent": { + "defaultMessage": "Removing this user may cause errors in the following {count, plural, one {course} other {courses}}:" + }, "system.admin.instance.instance.UsersButton.deletionSuccess": { "defaultMessage": "User was removed from this instance." }, @@ -9038,5 +9904,8 @@ }, "users.troubleSigningIn": { "defaultMessage": "Trouble signing in?" + }, + "wEQDC6": { + "defaultMessage": "Edit" } }