diff --git a/package.json b/package.json index d928744..b04f2ff 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,14 @@ "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", "axios": "^0.19.2", + "i18next": "^21.9.1", + "i18next-browser-languagedetector": "^6.1.5", "lodash": "^4.17.19", "luxon": "^1.25.0", "query-string": "^6.12.1", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-i18next": "^11.18.4", "react-redux": "^7.2.0", "react-router-dom": "^5.1.2", "react-scripts": "3.4.1", @@ -68,7 +71,7 @@ "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.19.0", - "eslint-plugin-react-hooks": "^3.0.0", + "eslint-plugin-react-hooks": "^2.5.0", "prettier": "^2.0.4" } } diff --git a/src/components/Auth/LoginForm.tsx b/src/components/Auth/LoginForm.tsx index 8413411..3abce1f 100644 --- a/src/components/Auth/LoginForm.tsx +++ b/src/components/Auth/LoginForm.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Button } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; @@ -32,6 +33,7 @@ const useStyles = makeStyles(authStyles); const LoginForm: React.FC = (props) => { const classes = useStyles(); + const { t } = useTranslation(); const { errors, fields, onChange, onSubmit } = props; const getError = (field: string): string => { @@ -47,7 +49,7 @@ const LoginForm: React.FC = (props) => {
= (props) => { /> = (props) => { color="primary" className={classes.submit} > - Sign In + {t('login')} ); diff --git a/src/components/Auth/PasswordResetForm.tsx b/src/components/Auth/PasswordResetForm.tsx index e36f4ce..37547cb 100644 --- a/src/components/Auth/PasswordResetForm.tsx +++ b/src/components/Auth/PasswordResetForm.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import { Button } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import { makeStyles } from '@material-ui/core/styles'; @@ -30,6 +32,7 @@ const useStyles = makeStyles(authStyles); const PasswordResetForm: React.FC = (props) => { const classes = useStyles(); + const { t } = useTranslation(); const { errors, fields, onChange, onSubmit } = props; const getError = (field: string): string => { @@ -45,7 +48,7 @@ const PasswordResetForm: React.FC = (props) => {
= (props) => { color="primary" className={classes.submit} > - Reset Password + {t('password_reset')} ); diff --git a/src/components/Auth/PasswordResetRequestConfirm.tsx b/src/components/Auth/PasswordResetRequestConfirm.tsx index eeaf222..fd32f66 100644 --- a/src/components/Auth/PasswordResetRequestConfirm.tsx +++ b/src/components/Auth/PasswordResetRequestConfirm.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import Confirm from './utils/Confirm'; interface PasswordResetRequestConfirmProps { @@ -8,11 +10,11 @@ interface PasswordResetRequestConfirmProps { const PasswordResetRequestConfirm: React.FC = (props) => { const { email } = props; + const { t } = useTranslation(); return ( - - To get back into your account, follow the instruction we've sent you to your {email} - email address. + + {t('reset_link_sent', { email })} ); }; diff --git a/src/components/Auth/PasswordResetRequestForm.tsx b/src/components/Auth/PasswordResetRequestForm.tsx index 2859c12..007e369 100644 --- a/src/components/Auth/PasswordResetRequestForm.tsx +++ b/src/components/Auth/PasswordResetRequestForm.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import { Button } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; @@ -22,6 +24,7 @@ const useStyles = makeStyles(authStyles); const PasswordResetRequestForm: React.FC = (props) => { const classes = useStyles(); + const { t } = useTranslation(); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); @@ -32,7 +35,7 @@ const PasswordResetRequestForm: React.FC = (props
= (props color="primary" className={classes.submit} > - Request Password Reset + {t('send_reset_link')} ); diff --git a/src/components/Auth/RegisterActivateFailed.tsx b/src/components/Auth/RegisterActivateFailed.tsx index a52efa6..7d5963d 100644 --- a/src/components/Auth/RegisterActivateFailed.tsx +++ b/src/components/Auth/RegisterActivateFailed.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import { Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; @@ -9,13 +11,14 @@ const useStyles = makeStyles(authStyles); const RegisterActivateFailed: React.FC = () => { const classes = useStyles(); + const { t } = useTranslation(); return ( <> - Account activation failed! + {t('activation_failed')} - Invalid token. + {t('invalid_token')} ); }; diff --git a/src/components/Auth/RegisterActivateSuccess.tsx b/src/components/Auth/RegisterActivateSuccess.tsx index 3d3d3e8..e3ca430 100644 --- a/src/components/Auth/RegisterActivateSuccess.tsx +++ b/src/components/Auth/RegisterActivateSuccess.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import Success from './utils/Success'; interface RegisterActivateSuccessProps { @@ -8,9 +10,10 @@ interface RegisterActivateSuccessProps { const RegisterActivateSuccess: React.FC = (props) => { const { onClick } = props; + const { t } = useTranslation(); return ( - + ); }; diff --git a/src/components/Auth/RegisterConfirm.tsx b/src/components/Auth/RegisterConfirm.tsx index a076860..1d85755 100644 --- a/src/components/Auth/RegisterConfirm.tsx +++ b/src/components/Auth/RegisterConfirm.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import Confirm from './utils/Confirm'; interface RegisterConfirmProps { @@ -8,11 +10,11 @@ interface RegisterConfirmProps { const RegisterConfirm: React.FC = (props) => { const { email } = props; + const { t } = useTranslation(); return ( - - To activate your account, follow the instruction we've sent you to your {email} - email address. + + {t('registration_confirmed', { email })} ); }; diff --git a/src/components/Auth/RegisterForm.tsx b/src/components/Auth/RegisterForm.tsx index d182702..d47987c 100644 --- a/src/components/Auth/RegisterForm.tsx +++ b/src/components/Auth/RegisterForm.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import { Button, Grid } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import { makeStyles } from '@material-ui/core/styles'; @@ -44,6 +46,7 @@ const useStyles = makeStyles(authStyles); const RegisterForm: React.FC = (props) => { const classes = useStyles(); + const { t } = useTranslation(); const { errors, fields, onChange, onSubmit } = props; const getError = (field: string): string => { @@ -61,7 +64,7 @@ const RegisterForm: React.FC = (props) => { = (props) => { = (props) => { = (props) => { = (props) => { = (props) => { = (props) => { = (props) => { = (props) => { color="primary" className={classes.submit} > - Register + {t('register')} ); diff --git a/src/components/Jury/SubmissionRater.tsx b/src/components/Jury/SubmissionRater.tsx index a2687c8..b9f8f8f 100644 --- a/src/components/Jury/SubmissionRater.tsx +++ b/src/components/Jury/SubmissionRater.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import { IconButton, makeStyles } from '@material-ui/core'; import { Close, NavigateBefore, NavigateNext } from '@material-ui/icons'; @@ -61,6 +63,7 @@ const SubmissionRater: React.FC = ({ }); const classes = useStyles(); + const { t } = useTranslation(); const { description, files, title } = submission; return ( @@ -101,9 +104,9 @@ const SubmissionRater: React.FC = ({
- Title: {title} + {t('title')}: {title}
- Description: + {t('description')}: {description}
diff --git a/src/components/Layout/Dropdown.tsx b/src/components/Layout/Dropdown.tsx index 1957dde..8748573 100644 --- a/src/components/Layout/Dropdown.tsx +++ b/src/components/Layout/Dropdown.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { useLocation } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { IconButton, Menu, MenuItem } from '@material-ui/core'; import AccountCircle from '@material-ui/icons/AccountCircle'; @@ -20,6 +21,7 @@ const useStyles = makeStyles({ const Dropdown: React.FC = () => { const user = React.useContext(userContext); const location = useLocation(); + const { t } = useTranslation(); const classes = useStyles(); const [anchorEl, setAnchorEl] = React.useState(null); @@ -39,7 +41,7 @@ const Dropdown: React.FC = () => { const publicMenu = [ - Register + {t('register')} , { onClick={closeMenu} key="link-2" > - Login + {t('login')} , ]; const privateMenu = [ - Profile + {t('profile')} , - Logout + {t('logout')} , ]; diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx index f5e2152..91394c9 100644 --- a/src/components/Layout/Header.tsx +++ b/src/components/Layout/Header.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; @@ -24,6 +25,11 @@ const CustomButton = ({ navigate, ...rest }: { navigate: Function }) => { const Header: React.FC = () => { const classes = useStyles(); + const { i18n, t } = useTranslation(); + + const handleLanguageChange = (event: React.MouseEvent): void => { + i18n.changeLanguage(event.currentTarget.value); + }; return ( @@ -37,7 +43,7 @@ const Header: React.FC = () => { className={classes.menuButton} color="inherit" > - Active contests + {t('active_contests')} { className={classes.menuButton} color="inherit" > - Edit submissions + {t('edit_submissions')} { className={classes.menuButton} color="inherit" > - Results + {t('results')} + + {i18n.language === 'en' ? ( + + ) : ( + + )} + diff --git a/src/components/Notifications/ConfirmDialog.tsx b/src/components/Notifications/ConfirmDialog.tsx index 40a5b39..88c2f86 100644 --- a/src/components/Notifications/ConfirmDialog.tsx +++ b/src/components/Notifications/ConfirmDialog.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@material-ui/core'; interface ConfirmDialogProps { @@ -13,6 +15,8 @@ interface ConfirmDialogProps { const ConfirmDialog: React.FC = (props) => { const { title, children, open, onClose, onConfirm } = props; + const { t } = useTranslation(); + const handleConfirm = () => { onClose(); onConfirm(); @@ -24,10 +28,10 @@ const ConfirmDialog: React.FC = (props) => { {children} diff --git a/src/components/SubmissionSetTable/SubmissionSetTable.tsx b/src/components/SubmissionSetTable/SubmissionSetTable.tsx index 31e7673..c865cc6 100644 --- a/src/components/SubmissionSetTable/SubmissionSetTable.tsx +++ b/src/components/SubmissionSetTable/SubmissionSetTable.tsx @@ -1,8 +1,10 @@ import React, { useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { AxiosPromise } from 'axios'; import _ from 'lodash'; import { + LabelDisplayedRowsArgs, Paper, Table, TableBody, @@ -39,6 +41,7 @@ const SubmissionSetTable: React.FC = ({ changePage, changePageSize, } = usePagination({ source: dataSource }); + const { t } = useTranslation(); const fetchPayments = useCallback(async (): Promise => { if (submissionSets.length === 0) { @@ -73,17 +76,25 @@ const SubmissionSetTable: React.FC = ({ await fetchPayments(); }; + const getLabelDisplayedRows = ({ + from, + to, + count: total, + }: LabelDisplayedRowsArgs): React.ReactNode => { + return t('displayed_rows', { from, to, total }); + }; + return ( <> - Name - Submited - Number of themes - Number of submissons - Paid? + {t('name')} + {t('submited')} + {t('n_themes')} + {t('n_submissions')} + {t('paid')} @@ -114,6 +125,8 @@ const SubmissionSetTable: React.FC = ({ page={page - 1} onChangePage={handlePageChange} onChangeRowsPerPage={handleChangeRowsPerPage} + labelRowsPerPage={t('rows_per_page')} + labelDisplayedRows={getLabelDisplayedRows} /> diff --git a/src/components/SubmissionSetTable/SubmissionSetTableRow.tsx b/src/components/SubmissionSetTable/SubmissionSetTableRow.tsx index f7db39e..d851bd4 100644 --- a/src/components/SubmissionSetTable/SubmissionSetTableRow.tsx +++ b/src/components/SubmissionSetTable/SubmissionSetTableRow.tsx @@ -2,6 +2,8 @@ import React from 'react'; import _ from 'lodash'; import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; + import { Button, Checkbox, TableCell, TableRow } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import AttachMoneyIcon from '@material-ui/icons/AttachMoney'; @@ -43,6 +45,7 @@ const SubmissionSetTableRow: React.FC = ({ onDelete, }: SubmissionSetTableRowProps) => { const classes = useStyles(); + const { t } = useTranslation(); const handlePaidChange = (event: React.ChangeEvent): void => { onPaidChange(submissionSet.id, event.target.checked); @@ -73,7 +76,7 @@ const SubmissionSetTableRow: React.FC = ({ className={classes.button} color="primary" > - View + {t('view')} diff --git a/src/components/Upload/Author/AuthorField.tsx b/src/components/Upload/Author/AuthorField.tsx index c7c05c5..77a2075 100644 --- a/src/components/Upload/Author/AuthorField.tsx +++ b/src/components/Upload/Author/AuthorField.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { WithTranslation, withTranslation } from 'react-i18next'; + import { withStyles, WithStyles } from '@material-ui/core/styles'; import { Card, CardContent, CardHeader, Grid } from '@material-ui/core'; @@ -11,7 +13,7 @@ import { AuthorModel, DateChange, InputChange } from '../../../types/models'; import DatePicker from '../DatePicker'; import AutocompleteField from '../AutocompleteInput'; -interface AuthorFieldProps extends WithStyles { +interface AuthorFieldProps extends WithStyles, WithTranslation { author: AuthorModel; showDob: boolean; showClub: boolean; @@ -33,12 +35,13 @@ class AuthorField extends React.Component { showSchool, requiredSchool, handleAUthorChange, + t, } = this.props; return ( @@ -48,7 +51,7 @@ class AuthorField extends React.Component { name="first_name" value={author.first_name} error={author.errors.first_name} - label="First name" + label={t('first_name')} autoComplete="given-name" autoFocus required @@ -62,7 +65,7 @@ class AuthorField extends React.Component { name="last_name" value={author.last_name} error={author.errors.last_name} - label="Last name" + label={t('last_name')} autoComplete="family-name" required onChange={handleAUthorChange} @@ -74,7 +77,7 @@ class AuthorField extends React.Component { { { name="mentor" value={author.mentor} error={author.errors.mentor} - label="Mentor" + label={t('mentor')} autoComplete="" onChange={handleAUthorChange} /> @@ -119,7 +122,7 @@ class AuthorField extends React.Component { name="club" value={author.club} error={author.errors.club} - label="Photo club" + label={t('photo_club')} required={requiredClub} autoComplete="" onChange={handleAUthorChange} @@ -134,7 +137,7 @@ class AuthorField extends React.Component { name="distinction" value={author.distinction} error={author.errors.distinction} - label="Photo distinction" + label={t('photo_distinction')} autoComplete="" onChange={handleAUthorChange} /> @@ -147,4 +150,4 @@ class AuthorField extends React.Component { } } -export default withStyles(uploadFormStyles)(AuthorField); +export default withTranslation()(withStyles(uploadFormStyles)(AuthorField)); diff --git a/src/components/Upload/Contest/ContestField.tsx b/src/components/Upload/Contest/ContestField.tsx index 2279478..4586c98 100644 --- a/src/components/Upload/Contest/ContestField.tsx +++ b/src/components/Upload/Contest/ContestField.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { WithTranslation, withTranslation } from 'react-i18next'; + import { withStyles, WithStyles } from '@material-ui/core/styles'; import { Button, CircularProgress, Grid, Typography } from '@material-ui/core'; @@ -12,7 +14,7 @@ import HeaderImage from '../../Layout/HeaderImage'; import AuthorField from '../Author/AuthorField'; import ThemeField from '../Theme/ThemeField'; -interface ContestFieldProps extends WithStyles { +interface ContestFieldProps extends WithStyles, WithTranslation { contest: ContestModel; handleAuthorChange: (payload: InputChange | DateChange) => void; handleSubmissionChange: (theme_id: number, submission_id: number, payload: InputChange) => void; @@ -28,7 +30,7 @@ interface ContestFieldProps extends WithStyles { class ContestField extends React.Component { render(): React.ReactNode { - const { classes } = this.props; + const { classes, t } = this.props; const { contest, @@ -92,7 +94,7 @@ class ContestField extends React.Component { })} {contest.errors.hasError && ( - Please fix errors before uploading. + {t('fix_errors')} )} {uploading ? ( @@ -107,7 +109,7 @@ class ContestField extends React.Component { color="primary" className={classes.submitButton} > - Upload + {t('upload')} )} @@ -117,4 +119,4 @@ class ContestField extends React.Component { } } -export default withStyles(uploadFormStyles)(ContestField); +export default withTranslation()(withStyles(uploadFormStyles)(ContestField)); diff --git a/src/components/Upload/ContestCard.tsx b/src/components/Upload/ContestCard.tsx index c95fd67..e6ca07a 100644 --- a/src/components/Upload/ContestCard.tsx +++ b/src/components/Upload/ContestCard.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import { Link } from 'react-router-dom'; import { @@ -25,8 +27,10 @@ const CustomButton = ({ navigate, ...rest }: { navigate: Function }) => { }; const ContestCard: React.FC = (props) => { + const { t } = useTranslation(); + const { contest } = props; - console.log(contest); + return ( = (props) => { component={CustomButton} color="primary" > - Open + {t('open')} diff --git a/src/components/Upload/Image/ImageButtons.tsx b/src/components/Upload/Image/ImageButtons.tsx index f1c92fd..36f0577 100644 --- a/src/components/Upload/Image/ImageButtons.tsx +++ b/src/components/Upload/Image/ImageButtons.tsx @@ -1,5 +1,7 @@ import React, { ReactChild } from 'react'; +import { useTranslation } from 'react-i18next'; + import { makeStyles } from '@material-ui/core/styles'; import { Button, Grid } from '@material-ui/core'; @@ -37,18 +39,20 @@ const StyledButton: React.FC = (props) => { const ImageButtons: React.FC = (props) => { const { handleChange, handleRemove, imageSelected } = props; + const { t } = useTranslation(); + const buttonWidth = imageSelected ? 6 : 12; return ( - {imageSelected ? 'Change' : 'Select'} + {imageSelected ? (t('change') as string) : (t('select') as string)} {imageSelected && ( - Remove + {t('remove') as string} )} diff --git a/src/components/Upload/Submission/DescriptionField.tsx b/src/components/Upload/Submission/DescriptionField.tsx index fe16f0b..3fe66d1 100644 --- a/src/components/Upload/Submission/DescriptionField.tsx +++ b/src/components/Upload/Submission/DescriptionField.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import { makeStyles } from '@material-ui/core/styles'; import { SubmissionModel, InputChange } from '../../../types/models'; @@ -16,6 +18,7 @@ const useStyles = makeStyles(uploadFormStyles); const DescriptionField: React.FC = (props) => { const classes = useStyles(); + const { t } = useTranslation(); const { submission, onChange } = props; return ( @@ -24,7 +27,7 @@ const DescriptionField: React.FC = (props) => { name="description" value={submission.description} error={submission.errors.description} - label="Description" + label={t('description')} autoComplete="" required={submission.meta.descriptionRequired} rows={3} diff --git a/src/components/Upload/Submission/TitleField.tsx b/src/components/Upload/Submission/TitleField.tsx index 2e7345d..a8bc093 100644 --- a/src/components/Upload/Submission/TitleField.tsx +++ b/src/components/Upload/Submission/TitleField.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; + import { SubmissionModel, InputChange } from '../../../types/models'; import InputField from '../InputField'; @@ -11,13 +13,14 @@ export interface TitleFieldProps { const TitleField: React.FC = (props) => { const { submission, onChange } = props; + const { t } = useTranslation(); return ( , document.getElementById('root')); diff --git a/src/views/EditSubmissionsList.tsx b/src/views/EditSubmissionsList.tsx index abb92e2..aa0c820 100644 --- a/src/views/EditSubmissionsList.tsx +++ b/src/views/EditSubmissionsList.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { groupBy, sum } from 'lodash'; +import { WithTranslation, withTranslation } from 'react-i18next'; import { withStyles, WithStyles } from '@material-ui/core/styles'; import { @@ -25,7 +26,7 @@ import SubmissionService from '../services/SubmissionService'; import { Author, Contest, Submission } from '../types/api'; import { asyncMap } from '../utils/async'; -interface EditSubmissionListProps extends WithStyles {} +interface EditSubmissionListProps extends WithStyles, WithTranslation {} interface EditSubmissionsListState { contests: Contest[]; @@ -88,7 +89,7 @@ class EditSubmissionsList extends React.Component< }; render() { - const { classes } = this.props; + const { classes, t } = this.props; if (!this.state.submissions.length) return ( @@ -113,15 +114,14 @@ class EditSubmissionsList extends React.Component< return ( <> - {' '}
- Contest - Author - Number of Themes - Number of Photos + {t('contest')} + {t('author')} + {t('n_themes')} + {t('n_photos')} @@ -142,7 +142,7 @@ class EditSubmissionsList extends React.Component< className={classes.button} disabled > - Edit + {t('edit')} @@ -152,7 +152,7 @@ class EditSubmissionsList extends React.Component< color="secondary" onClick={this.openDialog(group.submissions)} > - Delete + {t('delete')} @@ -162,15 +162,15 @@ class EditSubmissionsList extends React.Component< - Do you really want to delete the submission? + {t('delete_confirmation') as React.ReactChild} ); } } -export default withStyles(editListStyles)(EditSubmissionsList); +export default withTranslation()(withStyles(editListStyles)(EditSubmissionsList)); diff --git a/src/views/Login.tsx b/src/views/Login.tsx index cf530d6..2d89833 100644 --- a/src/views/Login.tsx +++ b/src/views/Login.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { withTranslation, WithTranslation } from 'react-i18next'; + import { Link as RouterLink, Redirect, RouteComponentProps, withRouter } from 'react-router-dom'; import { Card, CardContent, CardHeader, Grid, Link } from '@material-ui/core'; @@ -8,7 +10,7 @@ import { userContext } from '../components/Auth/AuthProvider'; import LoginForm, { Errors, Fields } from '../components/Auth/LoginForm'; import { IInputChangeEvent } from '../components/Upload/InputField'; -interface LoginViewProps extends RouteComponentProps {} +interface LoginViewProps extends RouteComponentProps, WithTranslation {} interface LoginViewState { fields: Fields; @@ -65,11 +67,13 @@ class LoginView extends React.Component { return ; } + const { t } = this.props; + return ( - + { to="/password-reset/request" variant="body2" > - Forgot password? + {t('forgot_password')} - Don't have an account? Register + {t('new_registration')} @@ -101,4 +105,4 @@ class LoginView extends React.Component { } } -export default withRouter(LoginView); +export default withTranslation()(withRouter(LoginView)); diff --git a/src/views/PasswordReset.tsx b/src/views/PasswordReset.tsx index d371d2a..6c22811 100644 --- a/src/views/PasswordReset.tsx +++ b/src/views/PasswordReset.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { withTranslation, WithTranslation } from 'react-i18next'; + import { isString } from 'lodash'; import { Redirect, RouteComponentProps, withRouter } from 'react-router-dom'; @@ -13,7 +15,7 @@ import { IInputChangeEvent } from '../components/Upload/InputField'; import UserService from '../services/UserService'; -interface PasswordResetProps extends RouteComponentProps {} +interface PasswordResetProps extends RouteComponentProps, WithTranslation {} interface PasswordResetState { fields: Fields; @@ -68,6 +70,7 @@ class PasswordResetView extends React.Component; @@ -78,7 +81,7 @@ class PasswordResetView extends React.Component @@ -100,4 +103,4 @@ class PasswordResetView extends React.Component { +class PasswordResetRequestView extends React.Component< + PasswordResetRequestViewProps, + PasswordResetRequestViewState +> { state = { done: false, fields: { @@ -41,13 +51,14 @@ class PasswordResetRequestView extends React.Component<{}, PasswordResetRequestV render() { const { done, fields } = this.state; + const { t } = this.props; return ( @@ -68,4 +79,4 @@ class PasswordResetRequestView extends React.Component<{}, PasswordResetRequestV } } -export default PasswordResetRequestView; +export default withTranslation()(PasswordResetRequestView); diff --git a/src/views/Register.tsx b/src/views/Register.tsx index 559c22f..109bff9 100644 --- a/src/views/Register.tsx +++ b/src/views/Register.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { withTranslation, WithTranslation } from 'react-i18next'; + import { Link as RouterLink } from 'react-router-dom'; import { Card, CardContent, CardHeader, Grid, Link } from '@material-ui/core'; @@ -10,13 +12,15 @@ import { IInputChangeEvent } from '../components/Upload/InputField'; import UserService from '../services/UserService'; +interface RegisterViewProps extends WithTranslation {} + interface RegisterViewState { fields: Fields; errors: Errors; done: boolean; } -class RegisterView extends React.Component<{}, RegisterViewState> { +class RegisterView extends React.Component { state = { fields: { first_name: '', @@ -65,12 +69,16 @@ class RegisterView extends React.Component<{}, RegisterViewState> { render() { const { errors, done, fields } = this.state; + const { t } = this.props; return ( - + {done ? ( @@ -89,7 +97,7 @@ class RegisterView extends React.Component<{}, RegisterViewState> { to="/login" variant="body2" > - Already have an account? Log in + {t('already_have_account')} @@ -103,4 +111,4 @@ class RegisterView extends React.Component<{}, RegisterViewState> { } } -export default RegisterView; +export default withTranslation()(RegisterView); diff --git a/src/views/RegisterActivate.tsx b/src/views/RegisterActivate.tsx index 226f321..99603c0 100644 --- a/src/views/RegisterActivate.tsx +++ b/src/views/RegisterActivate.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { WithTranslation, withTranslation } from 'react-i18next'; + import { isString } from 'lodash'; import { Redirect, RouteComponentProps, withRouter } from 'react-router-dom'; @@ -12,7 +14,7 @@ import RegisterActivateSuccess from '../components/Auth/RegisterActivateSuccess' import UserService from '../services/UserService'; -interface RegisterActivateViewProps extends RouteComponentProps {} +interface RegisterActivateViewProps extends RouteComponentProps, WithTranslation {} interface RegisterActivateViewState { succeeded: boolean | null; @@ -47,6 +49,7 @@ class RegisterActivateView extends React.Component< render() { const { redirect, succeeded } = this.state; + const { t } = this.props; if (redirect) { return ; @@ -61,7 +64,7 @@ class RegisterActivateView extends React.Component< @@ -78,4 +81,4 @@ class RegisterActivateView extends React.Component< } } -export default withRouter(RegisterActivateView); +export default withTranslation()(withRouter(RegisterActivateView)); diff --git a/src/views/ResultsList.tsx b/src/views/ResultsList.tsx index 3fa6b61..c4378ea 100644 --- a/src/views/ResultsList.tsx +++ b/src/views/ResultsList.tsx @@ -1,5 +1,11 @@ import React from 'react'; -const ResultsListView = () => <>Results; +import { useTranslation } from 'react-i18next'; + +const ResultsListView: React.FC<{}> = () => { + const { t } = useTranslation(); + + return <>{t('results')}; +}; export default ResultsListView; diff --git a/src/views/admin/SubmissionSetList.tsx b/src/views/admin/SubmissionSetList.tsx index 740a02f..0734bbf 100644 --- a/src/views/admin/SubmissionSetList.tsx +++ b/src/views/admin/SubmissionSetList.tsx @@ -14,6 +14,7 @@ import ContestService from '../../services/ContestService'; import { Contest, PaginatedResponse, SubmissionSet } from '../../types/api'; import SubmissionSetTable from '../../components/SubmissionSetTable/SubmissionSetTable'; import LoadingProgress from '../../components/LoadingProgress'; +import { WithTranslation, withTranslation } from 'react-i18next'; interface RouteMatchParams { contestId: string; @@ -27,6 +28,7 @@ interface SubmissionSetListState { interface SubmissionSetListProps extends WithStyles, + WithTranslation, RouteComponentProps {} class SubmissionSetList extends React.Component { @@ -72,6 +74,7 @@ class SubmissionSetList extends React.Component; @@ -97,15 +100,15 @@ class SubmissionSetList extends React.Component - Do you really want to delete the submission? + {t('delete_confirmation') as React.ReactChild} ); } } -export default withStyles(editListStyles)(withRouter(SubmissionSetList)); +export default withTranslation()(withStyles(editListStyles)(withRouter(SubmissionSetList))); diff --git a/src/views/admin/SubmissionSetView.tsx b/src/views/admin/SubmissionSetView.tsx index 4bb2e9d..43c5fda 100644 --- a/src/views/admin/SubmissionSetView.tsx +++ b/src/views/admin/SubmissionSetView.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { withRouter, RouteComponentProps } from 'react-router'; +import { withTranslation, WithTranslation } from 'react-i18next'; + import { Card, Grid } from '@material-ui/core'; import { Contest, SubmissionSet } from '../../types/api'; @@ -13,16 +15,15 @@ interface RouteMatchParams { submissionSetId: string; } +interface SubmissionListProps extends RouteComponentProps, WithTranslation {} + interface SubmissionListState { contest: Contest | null; submissionSet: SubmissionSet | null; } -class SubmissionList extends React.Component< - RouteComponentProps, - SubmissionListState -> { - constructor(props: RouteComponentProps) { +class SubmissionList extends React.Component { + constructor(props: SubmissionListProps) { super(props); this.state = { @@ -62,13 +63,15 @@ class SubmissionList extends React.Component< render(): React.ReactNode { const { contest, submissionSet } = this.state; + const { t } = this.props; return ( <> - Contest: {contest?.title} + {t('contest')}: {contest?.title}
- Author: {submissionSet ? this.getSubmissionSetAuthor(submissionSet) : ''} + {t('author')}:{' '} + {submissionSet ? this.getSubmissionSetAuthor(submissionSet) : ''}

@@ -92,13 +95,13 @@ class SubmissionList extends React.Component< /> ))}
- Title: {submission.title} + {t('title')}: {submission.title}
- Theme: {theme?.title} + {t('theme')}: {theme?.title} {submission.description && ( <>
- Description: {submission.description} + {t('description')}: {submission.description} )}
@@ -111,4 +114,4 @@ class SubmissionList extends React.Component< } } -export default withRouter(SubmissionList); +export default withTranslation()(withRouter(SubmissionList)); diff --git a/src/views/judge/SelectTheme.tsx b/src/views/judge/SelectTheme.tsx index 9aa030b..c667c89 100644 --- a/src/views/judge/SelectTheme.tsx +++ b/src/views/judge/SelectTheme.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { Button, @@ -21,6 +22,7 @@ const CustomButton = ({ navigate, ...rest }: { navigate: Function }) => { const SelectTheme: React.FC = () => { const [contests, setContests] = useState([]); + const { t } = useTranslation(); useEffect(() => { const fetchContests = async (): Promise => { @@ -54,7 +56,7 @@ const SelectTheme: React.FC = () => { component={CustomButton} color="primary" > - Open + {t('open')} diff --git a/src/views/results/SelectTheme.tsx b/src/views/results/SelectTheme.tsx index df0587b..38ebc8a 100644 --- a/src/views/results/SelectTheme.tsx +++ b/src/views/results/SelectTheme.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Link, useParams } from 'react-router-dom'; import { @@ -27,6 +28,7 @@ const CustomButton = ({ navigate, ...rest }: { navigate: Function }) => { const SelectTheme: React.FC = () => { const [contest, setContest] = useState(null); const { contestId } = useParams(); + const { t } = useTranslation(); useEffect(() => { const fetchContest = async (): Promise => { @@ -57,7 +59,7 @@ const SelectTheme: React.FC = () => { component={CustomButton} color="primary" > - View + {t('view')}