Skip to content

Commit 995d85d

Browse files
committed
Refactor side content to use translations
1 parent b624eac commit 995d85d

File tree

5 files changed

+101
-80
lines changed

5 files changed

+101
-80
lines changed

src/commons/sideContent/content/SideContentAutograder.tsx

Lines changed: 52 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Button, Collapse, Icon, PopoverPosition, Tooltip } from '@blueprintjs/core';
22
import { IconNames } from '@blueprintjs/icons';
3-
import React from 'react';
3+
import { useCallback, useMemo, useState } from 'react';
4+
import { useTranslation } from 'react-i18next';
45

56
import { AutogradingResult, Testcase } from '../../assessment/AssessmentTypes';
67
import ControlButton from '../../ControlButton';
@@ -28,12 +29,50 @@ type OwnProps = {
2829
};
2930

3031
const SideContentAutograder: React.FC<SideContentAutograderProps> = props => {
31-
const [showsTestcases, setTestcasesShown] = React.useState(true);
32-
const [showsResults, setResultsShown] = React.useState(true);
32+
const { t } = useTranslation('sideContent', { keyPrefix: 'autograder' });
33+
const [showsTestcases, setTestcasesShown] = useState(true);
34+
const [showsResults, setResultsShown] = useState(true);
3335

3436
const { testcases, autogradingResults, handleTestcaseEval, workspaceLocation } = props;
3537

36-
const testcaseCards = React.useMemo(
38+
const autograderTooltip = useMemo(
39+
() => (
40+
<div className="autograder-help-tooltip">
41+
<p>{t('tooltip.clickTestcase')}</p>
42+
<p>{t('tooltip.executeAll')}</p>
43+
<p>{t('tooltip.backgroundInfo')}</p>
44+
<p>{t('tooltip.privateTestcases')}</p>
45+
</div>
46+
),
47+
[t]
48+
);
49+
50+
const testcasesHeader = useMemo(
51+
() => (
52+
<div className="testcases-header" data-testid="testcases-header">
53+
{columnHeader('header-fn', t('headers.testcase'))}
54+
{columnHeader('header-expected', t('headers.expected'))}
55+
{columnHeader('header-actual', t('headers.actual'))}
56+
</div>
57+
),
58+
[t]
59+
);
60+
61+
const resultsHeader = useMemo(
62+
() => (
63+
<div className="results-header" data-testid="results-header">
64+
<div className="header-data">
65+
{columnHeader('header-sn', t('headers.sn'))}
66+
{columnHeader('header-status', t('headers.status'))}
67+
</div>
68+
{columnHeader('header-expected', t('headers.expected'))}
69+
{columnHeader('header-actual', t('headers.actual'))}
70+
</div>
71+
),
72+
[t]
73+
);
74+
75+
const testcaseCards = useMemo(
3776
() =>
3877
testcases.length > 0 ? (
3978
<div className="testcaseCards">
@@ -50,13 +89,13 @@ const SideContentAutograder: React.FC<SideContentAutograderProps> = props => {
5089
</div>
5190
) : (
5291
<div className="noResults" data-testid="noResults">
53-
There are no testcases provided for this question.
92+
{t('noTestcases')}
5493
</div>
5594
),
56-
[testcases, handleTestcaseEval, workspaceLocation]
95+
[testcases, testcasesHeader, t, handleTestcaseEval, workspaceLocation]
5796
);
5897

59-
const resultCards = React.useMemo(
98+
const resultCards = useMemo(
6099
() =>
61100
autogradingResults.length > 0 ? (
62101
<div>
@@ -67,17 +106,17 @@ const SideContentAutograder: React.FC<SideContentAutograderProps> = props => {
67106
</div>
68107
) : (
69108
<div className="noResults" data-testid="noResults">
70-
There are no results to show.
109+
{t('noResults')}
71110
</div>
72111
),
73-
[autogradingResults]
112+
[autogradingResults, resultsHeader, t]
74113
);
75114

76-
const toggleTestcases = React.useCallback(() => {
115+
const toggleTestcases = useCallback(() => {
77116
setTestcasesShown(!showsTestcases);
78117
}, [showsTestcases]);
79118

80-
const toggleResults = React.useCallback(() => setResultsShown(!showsResults), [showsResults]);
119+
const toggleResults = useCallback(() => setResultsShown(!showsResults), [showsResults]);
81120

82121
return (
83122
<div className="Autograder">
@@ -87,59 +126,29 @@ const SideContentAutograder: React.FC<SideContentAutograderProps> = props => {
87126
minimal={true}
88127
onClick={toggleTestcases}
89128
>
90-
<span>Testcases</span>
129+
<span>{t('testcases')}</span>
91130
<Tooltip content={autograderTooltip} placement={PopoverPosition.LEFT}>
92131
<Icon icon={IconNames.HELP} />
93132
</Tooltip>
94133
</Button>
95134
<Collapse isOpen={showsTestcases} keepChildrenMounted={true}>
96135
{testcaseCards}
97136
</Collapse>
98-
{collapseButton('Autograder Results', showsResults, toggleResults)}
137+
{collapseButton(t('results'), showsResults, toggleResults)}
99138
<Collapse isOpen={showsResults} keepChildrenMounted={true}>
100139
{resultCards}
101140
</Collapse>
102141
</div>
103142
);
104143
};
105144

106-
const autograderTooltip = (
107-
<div className="autograder-help-tooltip">
108-
<p>Click on each testcase below to execute it with the program in the editor.</p>
109-
<p>
110-
To execute all testcases at once, evaluate the program in the editor with this tab active.
111-
</p>
112-
<p>A green or red background indicates a passed or failed testcase respectively.</p>
113-
<p>Private testcases (only visible to staff when grading) have a grey background.</p>
114-
</div>
115-
);
116-
117145
const columnHeader = (colClass: string, colTitle: string) => (
118146
<div className={colClass}>
119147
{colTitle}
120148
<Icon icon={IconNames.CARET_DOWN} />
121149
</div>
122150
);
123151

124-
const testcasesHeader = (
125-
<div className="testcases-header" data-testid="testcases-header">
126-
{columnHeader('header-fn', 'Testcase')}
127-
{columnHeader('header-expected', 'Expected result')}
128-
{columnHeader('header-actual', 'Actual result')}
129-
</div>
130-
);
131-
132-
const resultsHeader = (
133-
<div className="results-header" data-testid="results-header">
134-
<div className="header-data">
135-
{columnHeader('header-sn', 'S/N')}
136-
{columnHeader('header-status', 'Testcase status')}
137-
</div>
138-
{columnHeader('header-expected', 'Expected result')}
139-
{columnHeader('header-actual', 'Actual result')}
140-
</div>
141-
);
142-
143152
const collapseButton = (label: string, isOpen: boolean, toggleFunc: () => void) => (
144153
<ControlButton
145154
label={label}

src/commons/sideContent/content/SideContentContestLeaderboard.tsx

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Button, Collapse, Icon, Tooltip } from '@blueprintjs/core';
22
import { IconNames } from '@blueprintjs/icons';
33
import React, { useMemo, useState } from 'react';
4+
import { useTranslation } from 'react-i18next';
45

56
import { ContestEntry } from '../../assessment/AssessmentTypes';
67
import { SideContentType } from '../SideContentTypes';
@@ -26,6 +27,7 @@ type StateProps = {
2627
* handleContestEntryClick: displays contest entry answer in assessment workspace editor}
2728
*/
2829
const SideContentContestLeaderboard: React.FC<SideContentContestLeaderboardProps> = props => {
30+
const { t } = useTranslation('sideContent', { keyPrefix: 'contestLeaderboard' });
2931
const { orderedContestEntries, handleContestEntryClick, leaderboardType } = props;
3032
const [showLeaderboard, setShowLeaderboard] = useState(true);
3133

@@ -35,19 +37,19 @@ const SideContentContestLeaderboard: React.FC<SideContentContestLeaderboardProps
3537

3638
const leaderboardTitle = useMemo(() => {
3739
return leaderboardType === SideContentType.scoreLeaderboard
38-
? 'Score Leaderboard'
40+
? t('titles.score')
3941
: leaderboardType === SideContentType.popularVoteLeaderboard
40-
? 'Popular Vote Leaderboard'
41-
: 'Contest Leaderboard';
42-
}, [leaderboardType]);
42+
? t('titles.popularVote')
43+
: t('titles.default');
44+
}, [leaderboardType, t]);
4345

4446
const contestLeaderboardTooltipContent = useMemo(() => {
4547
return leaderboardType === SideContentType.scoreLeaderboard
46-
? 'View the highest scoring contest entries!'
48+
? t('tooltips.score')
4749
: leaderboardType === SideContentType.popularVoteLeaderboard
48-
? 'View the most popular contest entries!'
49-
: 'View the top-rated contest entries!';
50-
}, [leaderboardType]);
50+
? t('tooltips.popularVote')
51+
: t('tooltips.default');
52+
}, [leaderboardType, t]);
5153

5254
const columnHeader = (colClass: string, colTitle: string) => (
5355
<div className={colClass}>
@@ -59,19 +61,19 @@ const SideContentContestLeaderboard: React.FC<SideContentContestLeaderboardProps
5961
const contestEntryHeader = useMemo(() => {
6062
return (
6163
<div className="leaderboard-header">
62-
{columnHeader('header-entryid', 'Student Name')}
63-
{columnHeader('header-entryrank', 'Rank')}
64+
{columnHeader('header-entryid', t('headers.studentName'))}
65+
{columnHeader('header-entryrank', t('headers.rank'))}
6466
{columnHeader(
6567
'header-score',
6668
leaderboardType === SideContentType.scoreLeaderboard
67-
? 'Calculated Score'
69+
? t('headers.score.calculated')
6870
: leaderboardType === SideContentType.popularVoteLeaderboard
69-
? 'Popularity Score'
70-
: 'Metric'
71+
? t('headers.score.popularity')
72+
: t('headers.score.default')
7173
)}
7274
</div>
7375
);
74-
}, [leaderboardType]);
76+
}, [leaderboardType, t]);
7577

7678
const contestEntryCards = useMemo(
7779
() => (
@@ -87,11 +89,11 @@ const SideContentContestLeaderboard: React.FC<SideContentContestLeaderboardProps
8789
/>
8890
))
8991
) : (
90-
<div className="noResults">There are no eligible contest leaderboard entries found.</div>
92+
<div className="noResults">{t('noEntries')}</div>
9193
)}
9294
</div>
9395
),
94-
[handleContestEntryClick, orderedContestEntries, contestEntryHeader]
96+
[handleContestEntryClick, orderedContestEntries, contestEntryHeader, t]
9597
);
9698

9799
return (

src/commons/sideContent/content/SideContentHtmlDisplay.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { IconNames } from '@blueprintjs/icons';
22
import { bindActionCreators } from '@reduxjs/toolkit';
3+
import { t } from 'i18next';
34
import React, { useEffect } from 'react';
5+
import { useTranslation } from 'react-i18next';
46
import { connect, MapDispatchToProps } from 'react-redux';
57
import { ResultOutput } from 'src/commons/application/ApplicationTypes';
68

@@ -20,6 +22,7 @@ type DispatchProps = {
2022
const ERROR_MESSAGE_REGEX = /^Line \d+: /i;
2123

2224
const SideContentHtmlDisplayBase: React.FC<OwnProps & DispatchProps> = props => {
25+
const { t } = useTranslation('sideContent', { keyPrefix: 'htmlDisplay' });
2326
const { content, handleAddHtmlConsoleError, alertSideContent } = props;
2427

2528
useEffect(() => {
@@ -45,7 +48,7 @@ const SideContentHtmlDisplayBase: React.FC<OwnProps & DispatchProps> = props =>
4548
return (
4649
<iframe
4750
className="sa-html-display"
48-
title="HTML Display"
51+
title={t('title')}
4952
sandbox="allow-scripts"
5053
srcDoc={JSON.parse(content)}
5154
src="about:blank"
@@ -69,7 +72,7 @@ const makeHtmlDisplayTabFrom = (
6972
handleError: (errorMsg: string) => void,
7073
workspaceLocation: SideContentLocation
7174
): SideContentTab => ({
72-
label: 'HTML Display',
75+
label: t('sideContent:htmlDisplay.label'),
7376
iconName: IconNames.MODAL,
7477
body: (
7578
<SideContentHtmlDisplay

src/commons/sideContent/content/SideContentResultCard.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
11
import { Card, Elevation, Pre } from '@blueprintjs/core';
22
import classNames from 'classnames';
3+
import type { TFunction } from 'i18next';
34
import React from 'react';
5+
import { useTranslation } from 'react-i18next';
46

57
import { AutogradingError, AutogradingResult } from '../../assessment/AssessmentTypes';
68

7-
const buildErrorString = (errors: AutogradingError[]) =>
8-
errors
9+
const buildErrorString = (
10+
t: TFunction<'sideContent', 'resultCard'>,
11+
errors: AutogradingError[]
12+
) => {
13+
return errors
914
.map(error => {
1015
switch (error.errorType) {
1116
case 'timeout':
12-
return '[TIMEOUT] Submission exceeded time limit for this test case.';
17+
return t('timeout');
1318
case 'syntax':
14-
return `[SYNTAX] Line ${error.line}: ${error.errorExplanation}`;
19+
return t('syntax', { line: error.line, errorExplanation: error.errorExplanation });
1520
case 'runtime':
16-
return `[RUNTIME] Line ${error.line}: ${error.errorExplanation}`;
21+
return t('runtime', { line: error.line, errorExplanation: error.errorExplanation });
1722
case 'systemError':
18-
return `[SYSTEM] ${error.errorMessage}`;
23+
return t('systemError', { errorMessage: error.errorMessage });
1924
default:
20-
return `[UNKNOWN] Autograder error: type ${error.errorType}`;
25+
return t('unknown', { errorType: error.errorType });
2126
}
2227
})
2328
.join('\n\n');
29+
};
2430

2531
type Props = {
2632
index: number;
2733
result: AutogradingResult;
2834
};
2935

3036
const SideContentResultCard: React.FC<Props> = ({ index, result }) => {
37+
const { t } = useTranslation('sideContent', { keyPrefix: 'resultCard' });
3138
return (
3239
<div
3340
className={classNames('ResultCard', result.resultType === 'pass' ? 'correct' : 'wrong')}
@@ -46,7 +53,7 @@ const SideContentResultCard: React.FC<Props> = ({ index, result }) => {
4653
{result.expected!}
4754
</Pre>
4855
<Pre className="result-actual" data-testid="result-actual">
49-
{result.resultType === 'error' ? buildErrorString(result.errors!) : result.actual!}
56+
{result.resultType === 'error' ? buildErrorString(t, result.errors!) : result.actual!}
5057
</Pre>
5158
</Card>
5259
</div>

src/commons/sideContent/content/SideContentUpload.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { FileInput } from '@blueprintjs/core';
22
import { IconNames } from '@blueprintjs/icons';
3+
import { t } from 'i18next';
34
import React, { useCallback } from 'react';
5+
import { useTranslation } from 'react-i18next';
46

57
import { SideContentTab, SideContentType } from '../SideContentTypes';
68

@@ -28,6 +30,7 @@ type Props = {
2830
* This component is responsible for uploading Java class files to bypass the compiler.
2931
*/
3032
const SideContentUpload: React.FC<Props> = ({ onUpload }) => {
33+
const { t } = useTranslation('sideContent', { keyPrefix: 'upload' });
3134
const [count, setCount] = React.useState(0);
3235

3336
const handleFileUpload: React.ChangeEventHandler<HTMLInputElement> = useCallback(
@@ -53,30 +56,27 @@ const SideContentUpload: React.FC<Props> = ({ onUpload }) => {
5356

5457
return (
5558
<div>
56-
<p>Bypass the compiler and type checker by uploading class files to run in the JVM.</p>
57-
<p>
58-
Only .class files are accepted. Code in the editor will be ignored when running while this
59-
tab is active.
60-
</p>
61-
<p>Compile the files with the following command:</p>
59+
<p>{t('description')}</p>
60+
<p>{t('acceptedFiles')}</p>
61+
<p>{t('compileCommand')}</p>
6262
<pre>
63-
<code>javac *.java -target 8 -source 8</code>
63+
<code>{t('javacCommand')}</code>
6464
</pre>
65-
<p>Avoid running class files downloaded from unknown sources.</p>
65+
<p>{t('warning')}</p>
6666
<p>
67-
<strong>Main class must be named Main and uploaded as Main.class.</strong>
67+
<strong>{t('mainClass')}</strong>
6868
</p>
6969
<FileInput
7070
inputProps={{ multiple: true, accept: '.class' }}
7171
onInputChange={handleFileUpload}
72-
text={count === 0 ? 'Choose files...' : `${count} file(s) uploaded.`}
72+
text={count === 0 ? t('chooseFiles') : `${count} ${t('filesUploaded')}`}
7373
/>
7474
</div>
7575
);
7676
};
7777

7878
const makeUploadTabFrom = (onUpload: (files: UploadResult) => void): SideContentTab => ({
79-
label: 'Upload files',
79+
label: t('sideContent:upload.label'),
8080
iconName: IconNames.Upload,
8181
body: <SideContentUpload onUpload={onUpload} />,
8282
id: SideContentType.upload

0 commit comments

Comments
 (0)