@tinyweb_dev/oe-exam-sdk 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/questions/_shared/components/compact/KidPassage.d.ts +4 -0
- package/dist/components/questions/_shared/components/compact/KidPassage.js +21 -0
- package/dist/components/questions/_shared/components/compact/KidQuestionCard.d.ts +5 -2
- package/dist/components/questions/_shared/components/compact/KidQuestionCard.js +4 -2
- package/dist/components/questions/_shared/components/compact/SpeakingReplyMic.d.ts +7 -0
- package/dist/components/questions/_shared/components/compact/SpeakingReplyMic.js +86 -0
- package/dist/components/questions/_shared/components/compact/index.d.ts +2 -0
- package/dist/components/questions/_shared/components/compact/index.js +2 -0
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +14 -5
- package/dist/components/questions/types/answer-the-question-group/map-answer-the-question-group-data.js +2 -2
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +35 -55
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.d.ts +4 -1
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +4 -4
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +2 -75
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +32 -35
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +2 -8
- package/dist/components/questions/types/word-fill-structured-form/map-wfsf-question-data.js +4 -11
- package/dist/components/questions/types/word-fill-structured-form/transform.js +4 -9
- package/dist/components/questions/viewer/QuestionViewer.d.ts +3 -1
- package/dist/components/questions/viewer/QuestionViewer.js +36 -19
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +1 -1
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/lib/i18n/messages/en/common.d.ts +38 -0
- package/dist/shared/lib/i18n/messages/en/common.js +39 -0
- package/dist/shared/lib/i18n/messages/en.d.ts +38 -0
- package/dist/shared/lib/i18n/messages/vi/common.d.ts +38 -0
- package/dist/shared/lib/i18n/messages/vi/common.js +39 -0
- package/dist/shared/lib/i18n/messages/vi.d.ts +38 -0
- package/dist/shared/lib/utils/media-urls.d.ts +2 -0
- package/dist/shared/lib/utils/media-urls.js +23 -0
- package/package.json +1 -1
package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
4
|
-
import { Check, X,
|
|
4
|
+
import { Check, X, Library } from 'lucide-react';
|
|
5
|
+
import { useT } from '../../../../shared/lib/i18n';
|
|
5
6
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
7
|
+
import { toMediaUrlList } from '../../../../shared/lib/utils/media-urls';
|
|
6
8
|
import { cn } from '../../../../shared/lib/utils';
|
|
7
|
-
import {
|
|
9
|
+
import { KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
|
|
8
10
|
import { detectWfsfBankMode, isOptionsWordEntry, normalizeSharedWordBank, } from './wfsf-word-bank.utils';
|
|
9
11
|
import { expandWfsfCorrectAlts, formatWfsfCorrectLabel, isWfsfBlankCorrect, primaryWfsfAnswers, } from '../../../../shared/lib/utils/wfsf-blank-match';
|
|
12
|
+
function wfsfFilledText(value) {
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return String(value[0] ?? '');
|
|
15
|
+
return value == null ? '' : String(value);
|
|
16
|
+
}
|
|
10
17
|
function normalizeWfsfBlankStyle(raw) {
|
|
11
18
|
const v = String(raw || '').toLowerCase();
|
|
12
19
|
if (v === 'letter' || v === 'box')
|
|
@@ -61,28 +68,14 @@ function ClientAudioItem({ url }) {
|
|
|
61
68
|
return null;
|
|
62
69
|
return (_jsx("div", { className: "w-full max-w-md", children: _jsx("audio", { controls: true, src: src, className: "w-full h-10 rounded-md" }) }));
|
|
63
70
|
}
|
|
64
|
-
const DEFAULT_WFSF_HEADER = 'Điền từ vào biểu mẫu có cấu trúc';
|
|
65
71
|
export function WordFillStructuredFormClient({ questionData, isReviewMode = false, userAnswer, onAnswerChange, autoFillCorrectAnswers = false, instruction, questionNumber, explanation, }) {
|
|
66
72
|
const { title = '', content = '', answers = {}, caseSensitive = false, blankStyle: rawBlankStyle, viewMode, wordBank = [], instruction: dataInstruction, } = questionData;
|
|
73
|
+
const t = useT();
|
|
67
74
|
const blankStyle = normalizeWfsfBlankStyle(rawBlankStyle);
|
|
68
75
|
const isLetterBlank = blankStyle === 'letter';
|
|
69
76
|
const isPhraseBlank = blankStyle === 'phrase' || blankStyle === 'translate';
|
|
70
|
-
const allImages = useMemo(() =>
|
|
71
|
-
|
|
72
|
-
return questionData.imageUrls.filter(Boolean);
|
|
73
|
-
}
|
|
74
|
-
if (questionData.imageUrl)
|
|
75
|
-
return [questionData.imageUrl];
|
|
76
|
-
return [];
|
|
77
|
-
}, [questionData.imageUrls, questionData.imageUrl]);
|
|
78
|
-
const allAudios = useMemo(() => {
|
|
79
|
-
if (Array.isArray(questionData.audioUrls) && questionData.audioUrls.length > 0) {
|
|
80
|
-
return questionData.audioUrls.filter(Boolean);
|
|
81
|
-
}
|
|
82
|
-
if (questionData.audioUrl)
|
|
83
|
-
return [questionData.audioUrl];
|
|
84
|
-
return [];
|
|
85
|
-
}, [questionData.audioUrls, questionData.audioUrl]);
|
|
77
|
+
const allImages = useMemo(() => toMediaUrlList(questionData.imageUrls?.length ? questionData.imageUrls : questionData.imageUrl), [questionData.imageUrls, questionData.imageUrl]);
|
|
78
|
+
const allAudios = useMemo(() => toMediaUrlList(questionData.audioUrls?.length ? questionData.audioUrls : questionData.audioUrl), [questionData.audioUrls, questionData.audioUrl]);
|
|
86
79
|
const [filledAnswers, setFilledAnswers] = useState(() => {
|
|
87
80
|
if (userAnswer)
|
|
88
81
|
return { ...userAnswer };
|
|
@@ -133,14 +126,18 @@ export function WordFillStructuredFormClient({ questionData, isReviewMode = fals
|
|
|
133
126
|
return a.localeCompare(b);
|
|
134
127
|
});
|
|
135
128
|
}, [content]);
|
|
136
|
-
const allFilled = expectedBlankKeys.every(k => filledAnswers[k]
|
|
137
|
-
const isAllCorrect = isReviewMode && expectedBlankKeys.every((k) => isWfsfBlankCorrect(filledAnswers[k]
|
|
129
|
+
const allFilled = expectedBlankKeys.every((k) => wfsfFilledText(filledAnswers[k]).trim());
|
|
130
|
+
const isAllCorrect = isReviewMode && expectedBlankKeys.every((k) => isWfsfBlankCorrect(wfsfFilledText(filledAnswers[k]), answers[k], caseSensitive));
|
|
138
131
|
// Header = page rubric. Pearson preview maps draft.instruction → explanation.
|
|
139
132
|
const rawExplanation = String(explanation || questionData.explanation || '').trim();
|
|
140
133
|
const headerInstruction = String(instruction || dataInstruction || rawExplanation || '').trim();
|
|
141
|
-
|
|
134
|
+
// Header reads "Bài x: rubric" — the question type name is never shown.
|
|
135
|
+
const numberLabel = typeof questionNumber === 'number' && questionNumber > 0
|
|
136
|
+
? t('question.viewer.partN', { n: questionNumber })
|
|
137
|
+
: '';
|
|
138
|
+
const headerTitle = numberLabel ? (_jsxs(_Fragment, { children: [_jsxs("strong", { className: "font-bold", children: [numberLabel, headerInstruction ? ':' : ''] }), headerInstruction ? ` ${headerInstruction}` : ''] })) : (headerInstruction || t('question.viewer.wfsfDefaultTitle'));
|
|
142
139
|
// Do not re-show the same rubric under "Giải thích".
|
|
143
|
-
const displayExplanation = rawExplanation && rawExplanation !==
|
|
140
|
+
const displayExplanation = rawExplanation && rawExplanation !== headerInstruction ? rawExplanation : '';
|
|
144
141
|
const handleInputChange = useCallback((key, value) => {
|
|
145
142
|
if (isReviewMode)
|
|
146
143
|
return;
|
|
@@ -155,7 +152,7 @@ export function WordFillStructuredFormClient({ questionData, isReviewMode = fals
|
|
|
155
152
|
setExpandedBlank(null);
|
|
156
153
|
}, [isReviewMode, onAnswerChange]);
|
|
157
154
|
const renderBlank = useCallback((key, reactKey) => {
|
|
158
|
-
const filled = filledAnswers[key]
|
|
155
|
+
const filled = wfsfFilledText(filledAnswers[key]);
|
|
159
156
|
const isCorrectFill = isReviewMode && isWfsfBlankCorrect(filled, answers[key], caseSensitive);
|
|
160
157
|
const isWrongFill = isReviewMode && !!filled && !isCorrectFill;
|
|
161
158
|
const blankOptions = optionsByKey.get(key);
|
|
@@ -178,8 +175,8 @@ export function WordFillStructuredFormClient({ questionData, isReviewMode = fals
|
|
|
178
175
|
: undefined, size: isLetterBlank ? undefined : isPhraseBlank ? 28 : 10, className: cn('inline-block bg-slate-50/40 text-center text-sm font-semibold outline-none transition-all align-baseline hover:bg-slate-100/60 focus:bg-teal-50/60 focus:ring-0', isLetterBlank
|
|
179
176
|
? 'h-7 w-7 rounded-md border px-0 uppercase'
|
|
180
177
|
: isPhraseBlank
|
|
181
|
-
? 'min-w-[16rem] max-w-[36rem] w-[28ch] rounded-
|
|
182
|
-
: 'min-w-[4.5rem] max-w-[8rem] w-[10ch] rounded-
|
|
178
|
+
? 'min-w-[16rem] max-w-[36rem] w-[28ch] rounded-none border-0 border-b-2 bg-transparent px-1 py-0.5'
|
|
179
|
+
: 'min-w-[4.5rem] max-w-[8rem] w-[10ch] rounded-none border-0 border-b-2 bg-transparent px-1 py-0.5', isReviewMode
|
|
183
180
|
? isCorrectFill
|
|
184
181
|
? 'border-green-400 bg-green-50 text-green-800'
|
|
185
182
|
: isWrongFill
|
|
@@ -278,12 +275,12 @@ export function WordFillStructuredFormClient({ questionData, isReviewMode = fals
|
|
|
278
275
|
.map((node, index) => renderNode(node, `root-${index}`))
|
|
279
276
|
.filter((node) => node !== null);
|
|
280
277
|
}, [content, renderBlank]);
|
|
281
|
-
return (
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
278
|
+
return (_jsx("div", { className: "space-y-4", children: _jsxs(KidQuestionCard, { hideIcon: true, title: headerTitle, titleClassName: "whitespace-normal break-words text-base font-normal text-gray-800", review: isReviewMode && allFilled ? (isAllCorrect ? 'correct' : 'wrong') : null, children: [title && (_jsx(KidPrompt, { children: _jsx("h2", { className: "text-sm font-semibold text-gray-800", children: title }) })), allAudios.length > 0 && (_jsx("div", { className: "flex flex-col items-center gap-2", children: allAudios.map((audio, idx) => (_jsx(ClientAudioItem, { url: audio }, idx))) })), allImages.length > 0 && (_jsx("div", { className: "flex flex-wrap justify-center gap-3", children: allImages.map((img, idx) => (_jsx(ClientImageItem, { url: img, alt: title ? `${title} (${idx + 1})` : undefined }, idx))) })), bankMode === 'WITH_WORD_BANK' && sharedWords.length > 0 && (_jsxs("div", { className: "rounded-lg border border-purple-200 bg-gradient-to-r from-purple-50 to-pink-50 p-3", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(Library, { className: "h-4 w-4 text-purple-600" }), _jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-purple-700", children: "Word Bank" })] }), _jsx("div", { className: "flex flex-wrap gap-2", children: sharedWords.map((item, index) => {
|
|
279
|
+
const used = usedSharedWords.has(item.word.trim().toLowerCase());
|
|
280
|
+
return (_jsxs("span", { className: cn('rounded-full border px-3 py-1 text-sm font-medium', item.isExample
|
|
281
|
+
? 'border-amber-300 bg-amber-100 text-amber-800'
|
|
282
|
+
: used
|
|
283
|
+
? 'border-purple-200 bg-white/60 text-purple-400 line-through'
|
|
284
|
+
: 'border-purple-300 bg-white text-purple-800'), children: [item.word, item.isExample ? ' (ex)' : ''] }, `${item.word}-${index}`));
|
|
285
|
+
}) })] })), _jsx("div", { className: "structured-form-content space-y-2 rounded-lg border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-5 text-base leading-loose text-gray-800", children: renderedContent }), isReviewMode && !isAllCorrect && (_jsxs("div", { className: "rounded-lg border border-green-200 bg-green-50 p-3", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-green-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: expectedBlankKeys.map((key) => (_jsxs("span", { className: "rounded-lg border border-green-300 bg-green-100 px-3 py-1.5 text-sm font-semibold text-green-800", children: [`{${key}}`, ": ", formatWfsfCorrectLabel(answers[key])] }, key))) })] })), isReviewMode && displayExplanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-blue-600", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-blue-800", children: displayExplanation })] }))] }) }));
|
|
289
286
|
}
|
package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js
CHANGED
|
@@ -15,6 +15,7 @@ import { Textarea } from '../../../../components/ui/textarea';
|
|
|
15
15
|
import { Switch } from '../../../../components/ui/switch';
|
|
16
16
|
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
17
17
|
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
18
|
+
import { toMediaUrlList } from '../../../../shared/lib/utils/media-urls';
|
|
18
19
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
19
20
|
import { ImagePreview } from '../../../../components/ui/image-preview';
|
|
20
21
|
import { WordFillStructuredFormClient } from './WordFillStructuredFormClient';
|
|
@@ -141,14 +142,7 @@ export function WordFillStructuredFormCreator({ initialData, onSave, onCancel, o
|
|
|
141
142
|
const [explanation, setExplanation] = useState(initialData?.explanation || '');
|
|
142
143
|
const [blankStyle, setBlankStyle] = useState(initialData?.blankStyle ?? 'word');
|
|
143
144
|
// ── Multiple Images & Audios ────────────────────────────────────────────────
|
|
144
|
-
const [imageUrls, setImageUrls] = useState(() =>
|
|
145
|
-
if (Array.isArray(initialData?.imageUrls) && initialData.imageUrls.length > 0) {
|
|
146
|
-
return initialData.imageUrls.filter(Boolean);
|
|
147
|
-
}
|
|
148
|
-
if (initialData?.imageUrl)
|
|
149
|
-
return [initialData.imageUrl];
|
|
150
|
-
return [];
|
|
151
|
-
});
|
|
145
|
+
const [imageUrls, setImageUrls] = useState(() => toMediaUrlList(initialData?.imageUrls?.length ? initialData.imageUrls : initialData?.imageUrl));
|
|
152
146
|
const [audioUrls, setAudioUrls] = useState(() => {
|
|
153
147
|
if (Array.isArray(initialData?.audioUrls) && initialData.audioUrls.length > 0) {
|
|
154
148
|
return initialData.audioUrls.filter(Boolean);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toMediaUrlList } from '../../../../shared/lib/utils/media-urls';
|
|
1
2
|
import { normalizeWfsfAnswersMap } from '../../../../shared/lib/utils/wfsf-blank-match';
|
|
2
3
|
import { detectWfsfBankMode, normalizeOptionsWordBank, normalizeSharedWordBank, } from './wfsf-word-bank.utils';
|
|
3
4
|
function asRecord(value) {
|
|
@@ -79,19 +80,11 @@ export function mapQuestionToWfsfData(question) {
|
|
|
79
80
|
? normalizeOptionsWordBank(rawWordBank, blankKeys)
|
|
80
81
|
: undefined;
|
|
81
82
|
const groupPayload = asRecord(asRecord(source.questionGroup).payload);
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
? rawImageUrls.filter(Boolean)
|
|
85
|
-
: (apiContent.imageUrl || answer.imageUrl ? [String(apiContent.imageUrl || answer.imageUrl)] : []);
|
|
86
|
-
const rawAudioUrls = apiContent.audioUrls || answer.audioUrls;
|
|
87
|
-
const audioUrls = Array.isArray(rawAudioUrls)
|
|
88
|
-
? rawAudioUrls.filter(Boolean)
|
|
89
|
-
: (apiContent.audioUrl || answer.audioUrl || source.audio_url
|
|
90
|
-
? [String(apiContent.audioUrl || answer.audioUrl || source.audio_url)]
|
|
91
|
-
: []);
|
|
83
|
+
const imageUrls = toMediaUrlList(apiContent.imageUrls || answer.imageUrls || apiContent.imageUrl || answer.imageUrl);
|
|
84
|
+
const audioUrls = toMediaUrlList(apiContent.audioUrls || answer.audioUrls || apiContent.audioUrl || answer.audioUrl || source.audio_url);
|
|
92
85
|
return {
|
|
93
86
|
title: String(apiContent.title || answer.title || ''),
|
|
94
|
-
imageUrl: imageUrls[0] ||
|
|
87
|
+
imageUrl: imageUrls[0] || '',
|
|
95
88
|
imageUrls,
|
|
96
89
|
audioUrl: audioUrls[0] || String(apiContent.audioUrl || answer.audioUrl || source.audio_url || ''),
|
|
97
90
|
audioUrls,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toMediaUrlList } from '../../../../shared/lib/utils/media-urls';
|
|
1
2
|
import { compactWfsfAnswersMap } from '../../../../shared/lib/utils/wfsf-blank-match';
|
|
2
3
|
/**
|
|
3
4
|
* Transform WORD_FILL_STRUCTURED_FORM creator data → API format
|
|
@@ -19,17 +20,11 @@ export const transformWordFillStructuredForm = (question) => {
|
|
|
19
20
|
answerData.groupSubtitle ||
|
|
20
21
|
answerData.groupContent ||
|
|
21
22
|
answerData.groupImageUrl);
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
? rawImageUrls.filter(Boolean)
|
|
25
|
-
: (answerData.imageUrl ? [answerData.imageUrl] : []);
|
|
26
|
-
const rawAudioUrls = answerData.audioUrls;
|
|
27
|
-
const audioUrls = Array.isArray(rawAudioUrls)
|
|
28
|
-
? rawAudioUrls.filter(Boolean)
|
|
29
|
-
: (answerData.audioUrl ? [answerData.audioUrl] : []);
|
|
23
|
+
const imageUrls = toMediaUrlList(answerData.imageUrls ?? answerData.imageUrl);
|
|
24
|
+
const audioUrls = toMediaUrlList(answerData.audioUrls ?? answerData.audioUrl);
|
|
30
25
|
const apiContent = {
|
|
31
26
|
title: answerData.title || '',
|
|
32
|
-
imageUrl: imageUrls[0] ||
|
|
27
|
+
imageUrl: imageUrls[0] || '',
|
|
33
28
|
imageUrls,
|
|
34
29
|
audioUrl: audioUrls[0] || answerData.audioUrl || '',
|
|
35
30
|
audioUrls,
|
|
@@ -12,6 +12,8 @@ interface QuestionViewerProps {
|
|
|
12
12
|
partialScore?: number;
|
|
13
13
|
feedback?: string;
|
|
14
14
|
}>;
|
|
15
|
+
onItemRecorded?: (flattenIndex: number, blob: Blob) => void;
|
|
16
|
+
uploadingIndex?: number | null;
|
|
15
17
|
}
|
|
16
|
-
export declare function QuestionViewer({ question, onSubmitAnswer, isReviewMode, userAnswer, gradeDetails, }: QuestionViewerProps): import("react").JSX.Element;
|
|
18
|
+
export declare function QuestionViewer({ question, onSubmitAnswer, isReviewMode, userAnswer, gradeDetails, onItemRecorded, uploadingIndex, }: QuestionViewerProps): import("react").JSX.Element;
|
|
17
19
|
export {};
|
|
@@ -6,7 +6,7 @@ import { Input } from '../../../components/ui/input';
|
|
|
6
6
|
import { Check, Volume2, X } from 'lucide-react';
|
|
7
7
|
import { useRef, useState } from 'react';
|
|
8
8
|
import { toast } from 'sonner';
|
|
9
|
-
import { t } from '../../../shared/lib/i18n';
|
|
9
|
+
import { t, useT } from '../../../shared/lib/i18n';
|
|
10
10
|
import { ReadAndColorObjectsClient } from '../types/read-and-color-objects/ReadAndColorObjectsClient';
|
|
11
11
|
import { ArrangeLettersIntoWordsClient } from '../types/arrange-letters-into-words/ArrangeLettersIntoWordsClient';
|
|
12
12
|
import { WriteShortParagraphClient } from '../types/write-short-paragraph/WriteShortParagraphClient';
|
|
@@ -38,7 +38,14 @@ import { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
|
38
38
|
import { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
39
39
|
import { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
|
|
40
40
|
import { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
|
|
41
|
-
|
|
41
|
+
function readCueAudioKey(value) {
|
|
42
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
43
|
+
return null;
|
|
44
|
+
const key = value.audioKey;
|
|
45
|
+
return typeof key === 'string' && key.trim() !== '' ? key : null;
|
|
46
|
+
}
|
|
47
|
+
export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false, userAnswer, gradeDetails, onItemRecorded, uploadingIndex = null, }) {
|
|
48
|
+
const translate = useT();
|
|
42
49
|
const [selectedAnswer, setSelectedAnswer] = useState(userAnswer || null);
|
|
43
50
|
const [playCount, setPlayCount] = useState(0);
|
|
44
51
|
const audioRef = useRef(null);
|
|
@@ -214,6 +221,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
214
221
|
next[index] = value;
|
|
215
222
|
setFillBlankAnswers(next);
|
|
216
223
|
setSelectedAnswer(next);
|
|
224
|
+
onSubmitAnswer?.(next);
|
|
217
225
|
};
|
|
218
226
|
const isCorrect = (index, value) => {
|
|
219
227
|
const correctAnswer = blanks[index] ?? '';
|
|
@@ -517,11 +525,13 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
517
525
|
case 'ANSWER_THE_QUESTION_GROUP':
|
|
518
526
|
return (_jsx(AnswerTheQuestionGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
519
527
|
case 'SPEAKING_CUE_CARD':
|
|
520
|
-
return (_jsx(SpeakingCueCardClient, { questionData: mapQuestionToSpeakingCueCardData(question), isReviewMode: isReviewMode
|
|
528
|
+
return (_jsx(SpeakingCueCardClient, { questionData: mapQuestionToSpeakingCueCardData(question), isReviewMode: isReviewMode, audioKey: readCueAudioKey(userAnswer), uploading: uploadingIndex === 0, onRecorded: isReviewMode || !onItemRecorded
|
|
529
|
+
? undefined
|
|
530
|
+
: (blob) => onItemRecorded(0, blob) }));
|
|
521
531
|
case 'SPONTANEOUS_QA':
|
|
522
532
|
return (_jsx(SpontaneousQAClient, { questionData: mapQuestionToSpontaneousQAData(question), isReviewMode: isReviewMode }));
|
|
523
533
|
case 'SPONTANEOUS_QA_GROUP':
|
|
524
|
-
return (_jsx(SpontaneousQaGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, gradeDetails: gradeDetails, disabled: !onSubmitAnswer }));
|
|
534
|
+
return (_jsx(SpontaneousQaGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, gradeDetails: gradeDetails, onItemRecorded: isReviewMode ? undefined : onItemRecorded, uploadingIndex: uploadingIndex, disabled: !onSubmitAnswer && !onItemRecorded }));
|
|
525
535
|
case 'WORD_ORDER_AND_MATCH_GROUP':
|
|
526
536
|
case 'WORD_ORDER_AND_MATCH':
|
|
527
537
|
return (_jsx(WordOrderAndMatchGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
@@ -572,7 +582,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
572
582
|
case 'CHOOSE_THEN_ANSWER_GROUP':
|
|
573
583
|
return (_jsx(ChooseThenAnswerGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, gradeDetails: gradeDetails, onSubmitAnswer: onSubmitAnswer }));
|
|
574
584
|
default:
|
|
575
|
-
return
|
|
585
|
+
return _jsx("div", { children: t('question.viewer.unsupported', { type: question.type }) });
|
|
576
586
|
}
|
|
577
587
|
};
|
|
578
588
|
/**
|
|
@@ -588,25 +598,32 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
588
598
|
question.type === 'ANSWER_THE_QUESTION') {
|
|
589
599
|
return null;
|
|
590
600
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
return 'Câu hỏi';
|
|
595
|
-
}
|
|
601
|
+
const typeKey = `question.type.${question.type}`;
|
|
602
|
+
const typeLabel = translate(typeKey);
|
|
603
|
+
const typedTitle = typeLabel === typeKey ? translate('question.viewer.fallbackTitle') : typeLabel;
|
|
596
604
|
if (typeof question.content === 'string') {
|
|
597
|
-
return stripLightweightMarkdown(question.content) ||
|
|
605
|
+
return stripLightweightMarkdown(question.content) || typedTitle;
|
|
598
606
|
}
|
|
599
607
|
const c = question.content;
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
if (
|
|
603
|
-
return stripLightweightMarkdown(
|
|
608
|
+
const metaTitle = typeof c?.meta?.title === 'string' ? c.meta.title : '';
|
|
609
|
+
const explicitTitle = (typeof c?.title === 'string' && c.title) || metaTitle;
|
|
610
|
+
if (explicitTitle && explicitTitle.length <= 80 && !/\{\d+\}/.test(explicitTitle) && !/\*\*/.test(explicitTitle)) {
|
|
611
|
+
return stripLightweightMarkdown(explicitTitle) || typedTitle;
|
|
612
|
+
}
|
|
613
|
+
// Modern fill stems live entirely in the body; title is a short type label.
|
|
614
|
+
if (question.type === 'FILL_IN_BLANK' ||
|
|
615
|
+
question.type === 'WORD_FILL_PARAGRAPH') {
|
|
616
|
+
return typedTitle;
|
|
617
|
+
}
|
|
618
|
+
const raw = c?.question || c?.paragraph || '';
|
|
604
619
|
if (typeof raw === 'string' && (/\{\d+\}/.test(raw) || /\*\*/.test(raw))) {
|
|
605
|
-
return
|
|
620
|
+
return typedTitle;
|
|
606
621
|
}
|
|
607
|
-
|
|
622
|
+
const stripped = stripLightweightMarkdown(typeof raw === 'string' ? raw : '');
|
|
623
|
+
return stripped && stripped.length <= 80 ? stripped : typedTitle;
|
|
608
624
|
};
|
|
609
|
-
const
|
|
625
|
+
const isGroupQuestion = question.type.endsWith('_GROUP') || Boolean(question.isGroup);
|
|
626
|
+
const cardTitle = isGroupQuestion ? null : resolveCardTitle();
|
|
610
627
|
// WFSF and Crossword already have their own bordered shell — avoid double Card border.
|
|
611
628
|
if (question.type === 'WORD_FILL_STRUCTURED_FORM' ||
|
|
612
629
|
question.type === 'CROSSWORD_PUZZLE' ||
|
|
@@ -614,7 +631,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
614
631
|
question.type === 'ANSWER_THE_QUESTION') {
|
|
615
632
|
return (_jsx("div", { className: "space-y-4", children: renderQuestion() }));
|
|
616
633
|
}
|
|
617
|
-
return (_jsxs(Card, { children: [cardTitle ? (_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center justify-between", children: [_jsx("span", { className: "min-w-0 pr-2 text-base font-semibold leading-snug", children: cardTitle }),
|
|
634
|
+
return (_jsxs(Card, { children: [cardTitle ? (_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center justify-between", children: [_jsx("span", { className: "min-w-0 pr-2 text-base font-semibold leading-snug", children: cardTitle }), _jsx("span", { className: "shrink-0 text-sm font-normal text-gray-500", children: translate('question.viewer.points', { count: question.points }) })] }) })) : null, _jsx(CardContent, { className: cardTitle ? 'space-y-4' : 'space-y-4 pt-4', children: renderQuestion() })] }));
|
|
618
635
|
}
|
|
619
636
|
/** Drop lightweight markdown markers for plain header labels (not full token parse). */
|
|
620
637
|
function stripLightweightMarkdown(text) {
|
|
@@ -15,5 +15,5 @@ function readStudentAnswer(value) {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
export function SpontaneousQaGroupViewer({ question, isReviewMode = false, userAnswer, onItemRecorded, uploadingIndex, gradeDetails, disabled, }) {
|
|
18
|
-
return (_jsx(SpontaneousQaGroupClient, { questionData: mapQuestionToSpontaneousQaGroupData(question), isReviewMode: isReviewMode, userAnswer: readStudentAnswer(userAnswer), onItemRecorded: isReviewMode ? undefined : onItemRecorded, uploadingIndex: uploadingIndex, gradeDetails:
|
|
18
|
+
return (_jsx(SpontaneousQaGroupClient, { questionData: mapQuestionToSpontaneousQaGroupData(question), isReviewMode: isReviewMode, userAnswer: readStudentAnswer(userAnswer), onItemRecorded: isReviewMode ? undefined : onItemRecorded, uploadingIndex: uploadingIndex, gradeDetails: gradeDetails, disabled: disabled }));
|
|
19
19
|
}
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export * from './constants/QueryKeyConstant';
|
|
|
3
3
|
export * from './constants/exam-level.enum';
|
|
4
4
|
export * from './constants/routes';
|
|
5
5
|
export * from './lib/utils';
|
|
6
|
+
export { useLocaleStore } from './lib/stores/localeStore';
|
|
7
|
+
export type { Locale as ExamSdkLocale } from './lib/stores/localeStore';
|
|
6
8
|
export * from '../components/questions/_shared';
|
|
7
9
|
export * from './types';
|
|
8
10
|
export * from './lib/utils/question-transform';
|
package/dist/shared/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from './constants/QueryKeyConstant';
|
|
|
3
3
|
export * from './constants/exam-level.enum';
|
|
4
4
|
export * from './constants/routes';
|
|
5
5
|
export * from './lib/utils';
|
|
6
|
+
export { useLocaleStore } from './lib/stores/localeStore';
|
|
6
7
|
export * from '../components/questions/_shared';
|
|
7
8
|
export * from './types';
|
|
8
9
|
export * from './lib/utils/question-transform';
|
|
@@ -120,5 +120,43 @@ declare const common: {
|
|
|
120
120
|
readonly 'common.badge.source.createdByAdmin': "Created by admin";
|
|
121
121
|
readonly 'common.badge.source.createdByTeacher': "Created by teacher";
|
|
122
122
|
readonly 'common.badge.source.createdFromEBM': "Synced from EBM";
|
|
123
|
+
readonly 'question.viewer.fallbackTitle': "Question";
|
|
124
|
+
readonly 'question.viewer.points': "{count} pts";
|
|
125
|
+
readonly 'question.viewer.questionN': "Question {n}";
|
|
126
|
+
readonly 'question.viewer.partN': "Part {n}";
|
|
127
|
+
readonly 'question.viewer.example': "Example";
|
|
128
|
+
readonly 'question.viewer.correct': "Correct";
|
|
129
|
+
readonly 'question.viewer.wrong': "Incorrect";
|
|
130
|
+
readonly 'question.viewer.readingPassage': "Reading passage";
|
|
131
|
+
readonly 'question.viewer.placeholder': "Enter your answer…";
|
|
132
|
+
readonly 'question.viewer.yourAnswer': "Your answer";
|
|
133
|
+
readonly 'question.viewer.writePlaceholder': "Write your answer here…";
|
|
134
|
+
readonly 'question.viewer.answerKey': "Answer key";
|
|
135
|
+
readonly 'question.viewer.noAnswer': "No answer yet";
|
|
136
|
+
readonly 'question.viewer.notWritten': "Not written";
|
|
137
|
+
readonly 'question.viewer.sample': "Sample";
|
|
138
|
+
readonly 'question.viewer.chooseScore': "Choose {score}";
|
|
139
|
+
readonly 'question.viewer.writeScore': "Write {score}";
|
|
140
|
+
readonly 'question.viewer.expandPassage': "Show more";
|
|
141
|
+
readonly 'question.viewer.collapsePassage': "Show less";
|
|
142
|
+
readonly 'question.viewer.unsupported': "Unsupported question type: {type}";
|
|
143
|
+
readonly 'question.viewer.missingPrompt': "Question text is missing";
|
|
144
|
+
readonly 'question.viewer.imageAlt': "Image {n}";
|
|
145
|
+
readonly 'question.viewer.itemImageAlt': "Question {n} image {m}";
|
|
146
|
+
readonly 'question.viewer.atqgDefaultInstruction': "Read the text and answer the questions.";
|
|
147
|
+
readonly 'question.viewer.wfsfDefaultTitle': "Fill in the structured form";
|
|
148
|
+
readonly 'question.type.ANSWER_THE_QUESTION_GROUP': "Answer the questions";
|
|
149
|
+
readonly 'question.type.CHOOSE_THEN_ANSWER_GROUP': "Choose then answer";
|
|
150
|
+
readonly 'question.type.FILL_IN_BLANK': "Fill in the blank";
|
|
151
|
+
readonly 'question.type.FILL_IN_BLANK_GROUP': "Fill in the blanks";
|
|
152
|
+
readonly 'question.type.WORD_FILL_STRUCTURED_FORM': "Structured form";
|
|
153
|
+
readonly 'question.type.WORD_FILL_PARAGRAPH': "Fill the paragraph";
|
|
154
|
+
readonly 'question.type.MATCHING_WITH_LINES_GROUP': "Match the columns";
|
|
155
|
+
readonly 'question.type.MATCH_WORD_TO_PICTURE': "Match words to pictures";
|
|
156
|
+
readonly 'question.type.MATCH_WORD_TO_PICTURE_GROUP': "Match words to pictures";
|
|
157
|
+
readonly 'question.type.CROSSWORD_PUZZLE': "Crossword";
|
|
158
|
+
readonly 'question.type.FIND_WORDS_IN_MATRIX': "Find the words";
|
|
159
|
+
readonly 'question.type.CHOOSE_THE_CORRECT_ANSWER': "Choose the correct answer";
|
|
160
|
+
readonly 'question.type.CHOOSE_THE_CORRECT_ANSWER_GROUP': "Choose the correct answers";
|
|
123
161
|
};
|
|
124
162
|
export default common;
|
|
@@ -136,5 +136,44 @@ const common = {
|
|
|
136
136
|
'common.badge.source.createdByAdmin': 'Created by admin',
|
|
137
137
|
'common.badge.source.createdByTeacher': 'Created by teacher',
|
|
138
138
|
'common.badge.source.createdFromEBM': 'Synced from EBM',
|
|
139
|
+
// Question viewer chrome (homework / examine)
|
|
140
|
+
'question.viewer.fallbackTitle': 'Question',
|
|
141
|
+
'question.viewer.points': '{count} pts',
|
|
142
|
+
'question.viewer.questionN': 'Question {n}',
|
|
143
|
+
'question.viewer.partN': 'Part {n}',
|
|
144
|
+
'question.viewer.example': 'Example',
|
|
145
|
+
'question.viewer.correct': 'Correct',
|
|
146
|
+
'question.viewer.wrong': 'Incorrect',
|
|
147
|
+
'question.viewer.readingPassage': 'Reading passage',
|
|
148
|
+
'question.viewer.placeholder': 'Enter your answer…',
|
|
149
|
+
'question.viewer.yourAnswer': 'Your answer',
|
|
150
|
+
'question.viewer.writePlaceholder': 'Write your answer here…',
|
|
151
|
+
'question.viewer.answerKey': 'Answer key',
|
|
152
|
+
'question.viewer.noAnswer': 'No answer yet',
|
|
153
|
+
'question.viewer.notWritten': 'Not written',
|
|
154
|
+
'question.viewer.sample': 'Sample',
|
|
155
|
+
'question.viewer.chooseScore': 'Choose {score}',
|
|
156
|
+
'question.viewer.writeScore': 'Write {score}',
|
|
157
|
+
'question.viewer.expandPassage': 'Show more',
|
|
158
|
+
'question.viewer.collapsePassage': 'Show less',
|
|
159
|
+
'question.viewer.unsupported': 'Unsupported question type: {type}',
|
|
160
|
+
'question.viewer.missingPrompt': 'Question text is missing',
|
|
161
|
+
'question.viewer.imageAlt': 'Image {n}',
|
|
162
|
+
'question.viewer.itemImageAlt': 'Question {n} image {m}',
|
|
163
|
+
'question.viewer.atqgDefaultInstruction': 'Read the text and answer the questions.',
|
|
164
|
+
'question.viewer.wfsfDefaultTitle': 'Fill in the structured form',
|
|
165
|
+
'question.type.ANSWER_THE_QUESTION_GROUP': 'Answer the questions',
|
|
166
|
+
'question.type.CHOOSE_THEN_ANSWER_GROUP': 'Choose then answer',
|
|
167
|
+
'question.type.FILL_IN_BLANK': 'Fill in the blank',
|
|
168
|
+
'question.type.FILL_IN_BLANK_GROUP': 'Fill in the blanks',
|
|
169
|
+
'question.type.WORD_FILL_STRUCTURED_FORM': 'Structured form',
|
|
170
|
+
'question.type.WORD_FILL_PARAGRAPH': 'Fill the paragraph',
|
|
171
|
+
'question.type.MATCHING_WITH_LINES_GROUP': 'Match the columns',
|
|
172
|
+
'question.type.MATCH_WORD_TO_PICTURE': 'Match words to pictures',
|
|
173
|
+
'question.type.MATCH_WORD_TO_PICTURE_GROUP': 'Match words to pictures',
|
|
174
|
+
'question.type.CROSSWORD_PUZZLE': 'Crossword',
|
|
175
|
+
'question.type.FIND_WORDS_IN_MATRIX': 'Find the words',
|
|
176
|
+
'question.type.CHOOSE_THE_CORRECT_ANSWER': 'Choose the correct answer',
|
|
177
|
+
'question.type.CHOOSE_THE_CORRECT_ANSWER_GROUP': 'Choose the correct answers',
|
|
139
178
|
};
|
|
140
179
|
export default common;
|
|
@@ -2297,5 +2297,43 @@ declare const en: {
|
|
|
2297
2297
|
readonly 'common.badge.source.createdByAdmin': "Created by admin";
|
|
2298
2298
|
readonly 'common.badge.source.createdByTeacher': "Created by teacher";
|
|
2299
2299
|
readonly 'common.badge.source.createdFromEBM': "Synced from EBM";
|
|
2300
|
+
readonly 'question.viewer.fallbackTitle': "Question";
|
|
2301
|
+
readonly 'question.viewer.points': "{count} pts";
|
|
2302
|
+
readonly 'question.viewer.questionN': "Question {n}";
|
|
2303
|
+
readonly 'question.viewer.partN': "Part {n}";
|
|
2304
|
+
readonly 'question.viewer.example': "Example";
|
|
2305
|
+
readonly 'question.viewer.correct': "Correct";
|
|
2306
|
+
readonly 'question.viewer.wrong': "Incorrect";
|
|
2307
|
+
readonly 'question.viewer.readingPassage': "Reading passage";
|
|
2308
|
+
readonly 'question.viewer.placeholder': "Enter your answer…";
|
|
2309
|
+
readonly 'question.viewer.yourAnswer': "Your answer";
|
|
2310
|
+
readonly 'question.viewer.writePlaceholder': "Write your answer here…";
|
|
2311
|
+
readonly 'question.viewer.answerKey': "Answer key";
|
|
2312
|
+
readonly 'question.viewer.noAnswer': "No answer yet";
|
|
2313
|
+
readonly 'question.viewer.notWritten': "Not written";
|
|
2314
|
+
readonly 'question.viewer.sample': "Sample";
|
|
2315
|
+
readonly 'question.viewer.chooseScore': "Choose {score}";
|
|
2316
|
+
readonly 'question.viewer.writeScore': "Write {score}";
|
|
2317
|
+
readonly 'question.viewer.expandPassage': "Show more";
|
|
2318
|
+
readonly 'question.viewer.collapsePassage': "Show less";
|
|
2319
|
+
readonly 'question.viewer.unsupported': "Unsupported question type: {type}";
|
|
2320
|
+
readonly 'question.viewer.missingPrompt': "Question text is missing";
|
|
2321
|
+
readonly 'question.viewer.imageAlt': "Image {n}";
|
|
2322
|
+
readonly 'question.viewer.itemImageAlt': "Question {n} image {m}";
|
|
2323
|
+
readonly 'question.viewer.atqgDefaultInstruction': "Read the text and answer the questions.";
|
|
2324
|
+
readonly 'question.viewer.wfsfDefaultTitle': "Fill in the structured form";
|
|
2325
|
+
readonly 'question.type.ANSWER_THE_QUESTION_GROUP': "Answer the questions";
|
|
2326
|
+
readonly 'question.type.CHOOSE_THEN_ANSWER_GROUP': "Choose then answer";
|
|
2327
|
+
readonly 'question.type.FILL_IN_BLANK': "Fill in the blank";
|
|
2328
|
+
readonly 'question.type.FILL_IN_BLANK_GROUP': "Fill in the blanks";
|
|
2329
|
+
readonly 'question.type.WORD_FILL_STRUCTURED_FORM': "Structured form";
|
|
2330
|
+
readonly 'question.type.WORD_FILL_PARAGRAPH': "Fill the paragraph";
|
|
2331
|
+
readonly 'question.type.MATCHING_WITH_LINES_GROUP': "Match the columns";
|
|
2332
|
+
readonly 'question.type.MATCH_WORD_TO_PICTURE': "Match words to pictures";
|
|
2333
|
+
readonly 'question.type.MATCH_WORD_TO_PICTURE_GROUP': "Match words to pictures";
|
|
2334
|
+
readonly 'question.type.CROSSWORD_PUZZLE': "Crossword";
|
|
2335
|
+
readonly 'question.type.FIND_WORDS_IN_MATRIX': "Find the words";
|
|
2336
|
+
readonly 'question.type.CHOOSE_THE_CORRECT_ANSWER': "Choose the correct answer";
|
|
2337
|
+
readonly 'question.type.CHOOSE_THE_CORRECT_ANSWER_GROUP': "Choose the correct answers";
|
|
2300
2338
|
};
|
|
2301
2339
|
export default en;
|
|
@@ -120,5 +120,43 @@ declare const common: {
|
|
|
120
120
|
readonly 'common.badge.source.createdByAdmin': "Admin tạo";
|
|
121
121
|
readonly 'common.badge.source.createdByTeacher': "Giáo viên tạo";
|
|
122
122
|
readonly 'common.badge.source.createdFromEBM': "Đồng bộ EBM";
|
|
123
|
+
readonly 'question.viewer.fallbackTitle': "Câu hỏi";
|
|
124
|
+
readonly 'question.viewer.points': "{count} điểm";
|
|
125
|
+
readonly 'question.viewer.questionN': "Câu {n}";
|
|
126
|
+
readonly 'question.viewer.partN': "Bài {n}";
|
|
127
|
+
readonly 'question.viewer.example': "Câu ví dụ";
|
|
128
|
+
readonly 'question.viewer.correct': "Đúng";
|
|
129
|
+
readonly 'question.viewer.wrong': "Sai";
|
|
130
|
+
readonly 'question.viewer.readingPassage': "Bài đọc";
|
|
131
|
+
readonly 'question.viewer.placeholder': "Nhập câu trả lời...";
|
|
132
|
+
readonly 'question.viewer.yourAnswer': "Câu trả lời của bạn";
|
|
133
|
+
readonly 'question.viewer.writePlaceholder': "Viết câu trả lời tại đây...";
|
|
134
|
+
readonly 'question.viewer.answerKey': "Đáp án";
|
|
135
|
+
readonly 'question.viewer.noAnswer': "Chưa có đáp án";
|
|
136
|
+
readonly 'question.viewer.notWritten': "Chưa viết";
|
|
137
|
+
readonly 'question.viewer.sample': "Mẫu";
|
|
138
|
+
readonly 'question.viewer.chooseScore': "Chọn {score}";
|
|
139
|
+
readonly 'question.viewer.writeScore': "Viết {score}";
|
|
140
|
+
readonly 'question.viewer.expandPassage': "Xem thêm";
|
|
141
|
+
readonly 'question.viewer.collapsePassage': "Thu gọn";
|
|
142
|
+
readonly 'question.viewer.unsupported': "Loại câu hỏi không được hỗ trợ: {type}";
|
|
143
|
+
readonly 'question.viewer.missingPrompt': "Câu hỏi chưa được nhập";
|
|
144
|
+
readonly 'question.viewer.imageAlt': "Hình ảnh ({n})";
|
|
145
|
+
readonly 'question.viewer.itemImageAlt': "Hình ảnh câu {n} ({m})";
|
|
146
|
+
readonly 'question.viewer.atqgDefaultInstruction': "Đọc đoạn văn và trả lời câu hỏi.";
|
|
147
|
+
readonly 'question.viewer.wfsfDefaultTitle': "Điền từ vào biểu mẫu có cấu trúc";
|
|
148
|
+
readonly 'question.type.ANSWER_THE_QUESTION_GROUP': "Trả lời câu hỏi";
|
|
149
|
+
readonly 'question.type.CHOOSE_THEN_ANSWER_GROUP': "Chọn rồi trả lời";
|
|
150
|
+
readonly 'question.type.FILL_IN_BLANK': "Điền từ";
|
|
151
|
+
readonly 'question.type.FILL_IN_BLANK_GROUP': "Điền từ";
|
|
152
|
+
readonly 'question.type.WORD_FILL_STRUCTURED_FORM': "Điền từ vào biểu mẫu";
|
|
153
|
+
readonly 'question.type.WORD_FILL_PARAGRAPH': "Điền từ vào đoạn";
|
|
154
|
+
readonly 'question.type.MATCHING_WITH_LINES_GROUP': "Nối cột";
|
|
155
|
+
readonly 'question.type.MATCH_WORD_TO_PICTURE': "Nối từ với tranh";
|
|
156
|
+
readonly 'question.type.MATCH_WORD_TO_PICTURE_GROUP': "Nối từ với tranh";
|
|
157
|
+
readonly 'question.type.CROSSWORD_PUZZLE': "Ô chữ";
|
|
158
|
+
readonly 'question.type.FIND_WORDS_IN_MATRIX': "Tìm từ";
|
|
159
|
+
readonly 'question.type.CHOOSE_THE_CORRECT_ANSWER': "Chọn đáp án đúng";
|
|
160
|
+
readonly 'question.type.CHOOSE_THE_CORRECT_ANSWER_GROUP': "Chọn đáp án đúng";
|
|
123
161
|
};
|
|
124
162
|
export default common;
|
|
@@ -136,5 +136,44 @@ const common = {
|
|
|
136
136
|
'common.badge.source.createdByAdmin': 'Admin tạo',
|
|
137
137
|
'common.badge.source.createdByTeacher': 'Giáo viên tạo',
|
|
138
138
|
'common.badge.source.createdFromEBM': 'Đồng bộ EBM',
|
|
139
|
+
// Question viewer chrome (homework / examine)
|
|
140
|
+
'question.viewer.fallbackTitle': 'Câu hỏi',
|
|
141
|
+
'question.viewer.points': '{count} điểm',
|
|
142
|
+
'question.viewer.questionN': 'Câu {n}',
|
|
143
|
+
'question.viewer.partN': 'Bài {n}',
|
|
144
|
+
'question.viewer.example': 'Câu ví dụ',
|
|
145
|
+
'question.viewer.correct': 'Đúng',
|
|
146
|
+
'question.viewer.wrong': 'Sai',
|
|
147
|
+
'question.viewer.readingPassage': 'Bài đọc',
|
|
148
|
+
'question.viewer.placeholder': 'Nhập câu trả lời...',
|
|
149
|
+
'question.viewer.yourAnswer': 'Câu trả lời của bạn',
|
|
150
|
+
'question.viewer.writePlaceholder': 'Viết câu trả lời tại đây...',
|
|
151
|
+
'question.viewer.answerKey': 'Đáp án',
|
|
152
|
+
'question.viewer.noAnswer': 'Chưa có đáp án',
|
|
153
|
+
'question.viewer.notWritten': 'Chưa viết',
|
|
154
|
+
'question.viewer.sample': 'Mẫu',
|
|
155
|
+
'question.viewer.chooseScore': 'Chọn {score}',
|
|
156
|
+
'question.viewer.writeScore': 'Viết {score}',
|
|
157
|
+
'question.viewer.expandPassage': 'Xem thêm',
|
|
158
|
+
'question.viewer.collapsePassage': 'Thu gọn',
|
|
159
|
+
'question.viewer.unsupported': 'Loại câu hỏi không được hỗ trợ: {type}',
|
|
160
|
+
'question.viewer.missingPrompt': 'Câu hỏi chưa được nhập',
|
|
161
|
+
'question.viewer.imageAlt': 'Hình ảnh ({n})',
|
|
162
|
+
'question.viewer.itemImageAlt': 'Hình ảnh câu {n} ({m})',
|
|
163
|
+
'question.viewer.atqgDefaultInstruction': 'Đọc đoạn văn và trả lời câu hỏi.',
|
|
164
|
+
'question.viewer.wfsfDefaultTitle': 'Điền từ vào biểu mẫu có cấu trúc',
|
|
165
|
+
'question.type.ANSWER_THE_QUESTION_GROUP': 'Trả lời câu hỏi',
|
|
166
|
+
'question.type.CHOOSE_THEN_ANSWER_GROUP': 'Chọn rồi trả lời',
|
|
167
|
+
'question.type.FILL_IN_BLANK': 'Điền từ',
|
|
168
|
+
'question.type.FILL_IN_BLANK_GROUP': 'Điền từ',
|
|
169
|
+
'question.type.WORD_FILL_STRUCTURED_FORM': 'Điền từ vào biểu mẫu',
|
|
170
|
+
'question.type.WORD_FILL_PARAGRAPH': 'Điền từ vào đoạn',
|
|
171
|
+
'question.type.MATCHING_WITH_LINES_GROUP': 'Nối cột',
|
|
172
|
+
'question.type.MATCH_WORD_TO_PICTURE': 'Nối từ với tranh',
|
|
173
|
+
'question.type.MATCH_WORD_TO_PICTURE_GROUP': 'Nối từ với tranh',
|
|
174
|
+
'question.type.CROSSWORD_PUZZLE': 'Ô chữ',
|
|
175
|
+
'question.type.FIND_WORDS_IN_MATRIX': 'Tìm từ',
|
|
176
|
+
'question.type.CHOOSE_THE_CORRECT_ANSWER': 'Chọn đáp án đúng',
|
|
177
|
+
'question.type.CHOOSE_THE_CORRECT_ANSWER_GROUP': 'Chọn đáp án đúng',
|
|
139
178
|
};
|
|
140
179
|
export default common;
|