Webatrice: i18n login screen (#4584)
* i18n: login container and form * i18n: activate, host, and register forms * i18n: reset password forms * i18n: login dialogs, ICU formatting * i18n: login containers and components Co-authored-by: Jeremy Letto <jeremy.letto@datasite.com>
This commit is contained in:
parent
baaf261116
commit
f5b973e15c
35 changed files with 424 additions and 99 deletions
115
webclient/package-lock.json
generated
115
webclient/package-lock.json
generated
|
@ -1248,6 +1248,95 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@formatjs/ecma402-abstract": {
|
||||||
|
"version": "1.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.3.tgz",
|
||||||
|
"integrity": "sha512-kP/Buv5vVFMAYLHNvvUzr0lwRTU0u2WTy44Tqwku1X3C3lJ5dKqDCYVqA8wL+Y19Bq+MwHgxqd5FZJRCIsLRyQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/intl-localematcher": "0.2.24",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@formatjs/fast-memoize": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@formatjs/icu-messageformat-parser": {
|
||||||
|
"version": "2.0.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.0.18.tgz",
|
||||||
|
"integrity": "sha512-vquIzsAJJmZ5jWVH8dEgUKcbG4yu3KqtyPet+q35SW5reLOvblkfeCXTRW2TpIwNXzdVqsJBwjbTiRiSU9JxwQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.3",
|
||||||
|
"@formatjs/icu-skeleton-parser": "1.3.5",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@formatjs/icu-skeleton-parser": {
|
||||||
|
"version": "1.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.5.tgz",
|
||||||
|
"integrity": "sha512-Nhyo2/6kG7ZfgeEfo02sxviOuBcvtzH6SYUharj3DLCDJH3A/4OxkKcmx/2PWGX4bc6iSieh+FA94CsKDxnZBQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.3",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.2.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.24.tgz",
|
||||||
|
"integrity": "sha512-K/HRGo6EMnCbhpth/y3u4rW4aXkmQNqRe1L2G+Y5jNr3v0gYhvaucV8WixNju/INAMbPBlbsRBRo/nfjnoOnxQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@gar/promisify": {
|
"@gar/promisify": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz",
|
||||||
|
@ -8217,6 +8306,12 @@
|
||||||
"@babel/runtime": "^7.14.6"
|
"@babel/runtime": "^7.14.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"i18next-icu": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-icu/-/i18next-icu-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-sZ0VCWDnHysUYQL8j/0rVOxv6rLR+SBoaqQQ2UVNfLyJCuf/bAjYPkoUQgyuDkWFo1xZjeCf4G6GBNr7gD61bQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
@ -8370,6 +8465,26 @@
|
||||||
"side-channel": "^1.0.4"
|
"side-channel": "^1.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"intl-messageformat": {
|
||||||
|
"version": "9.11.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.11.4.tgz",
|
||||||
|
"integrity": "sha512-77TSkNubIy/hsapz6LQpyR6OADcxhWdhSaboPb5flMaALCVkPvAIxr48AlPqaMl4r1anNcvR9rpLWVdwUY1IKg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@formatjs/ecma402-abstract": "1.11.3",
|
||||||
|
"@formatjs/fast-memoize": "1.2.1",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.0.18",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"invariant": {
|
"invariant": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||||
|
|
|
@ -86,6 +86,8 @@
|
||||||
"@typescript-eslint/parser": "^5.3.1",
|
"@typescript-eslint/parser": "^5.3.1",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
"identity-obj-proxy": "^3.0.0"
|
"i18next-icu": "^2.0.3",
|
||||||
|
"identity-obj-proxy": "^3.0.0",
|
||||||
|
"intl-messageformat": "^9.11.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,27 @@
|
||||||
{
|
{
|
||||||
"Common": {
|
"Common": {
|
||||||
"language": "Translate into English.",
|
"language": "Translate into English.",
|
||||||
"disconnect": "Disconnect"
|
"disconnect": "Disconnect",
|
||||||
|
"label": {
|
||||||
|
"confirmPassword": "Confirm Password",
|
||||||
|
"confirmSure": "Are you sure?",
|
||||||
|
"country": "Country",
|
||||||
|
"delete": "Delete",
|
||||||
|
"email": "Email",
|
||||||
|
"hostName": "Host Name",
|
||||||
|
"hostAddress": "Host Address",
|
||||||
|
"password": "Password",
|
||||||
|
"passwordAgain": "Password Again",
|
||||||
|
"port": "Port",
|
||||||
|
"realName": "Real Name",
|
||||||
|
"saveChanges": "Save Changes",
|
||||||
|
"token": "Token",
|
||||||
|
"username": "Username"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"minChars": "Minimum of {count} {count, plural, one {character} other {characters}} required",
|
||||||
|
"passwordsMustMatch": "Passwords don't match",
|
||||||
|
"required": "Required"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
webclient/src/components/KnownHosts/KnownHosts.i18n.json
Normal file
7
webclient/src/components/KnownHosts/KnownHosts.i18n.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"KnownHosts": {
|
||||||
|
"label": "Host",
|
||||||
|
"add": "Add new host",
|
||||||
|
"toast": "Host successfully {mode, select, created {created} deleted {deleted} other {edited}}."
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Select, MenuItem } from '@material-ui/core';
|
import { Select, MenuItem } from '@material-ui/core';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import FormControl from '@material-ui/core/FormControl';
|
import FormControl from '@material-ui/core/FormControl';
|
||||||
|
@ -33,6 +34,7 @@ const KnownHosts = (props) => {
|
||||||
const { input: { onChange }, meta, disabled } = props;
|
const { input: { onChange }, meta, disabled } = props;
|
||||||
const { touched, error, warning } = meta;
|
const { touched, error, warning } = meta;
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [hostsState, setHostsState] = useState({
|
const [hostsState, setHostsState] = useState({
|
||||||
hosts: [],
|
hosts: [],
|
||||||
|
@ -168,7 +170,7 @@ const KnownHosts = (props) => {
|
||||||
</div>
|
</div>
|
||||||
) }
|
) }
|
||||||
|
|
||||||
<InputLabel id='KnownHosts-select'>Host</InputLabel>
|
<InputLabel id='KnownHosts-select'>{ t('KnownHosts.label') }</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
id='KnownHosts-select'
|
id='KnownHosts-select'
|
||||||
labelId='KnownHosts-label'
|
labelId='KnownHosts-label'
|
||||||
|
@ -181,7 +183,7 @@ const KnownHosts = (props) => {
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<Button value={hostsState.selectedHost} onClick={openAddKnownHostDialog}>
|
<Button value={hostsState.selectedHost} onClick={openAddKnownHostDialog}>
|
||||||
<span>Add new host</span>
|
<span>{ t('KnownHosts.add') }</span>
|
||||||
<AddIcon fontSize='small' color='primary' />
|
<AddIcon fontSize='small' color='primary' />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
@ -213,9 +215,9 @@ const KnownHosts = (props) => {
|
||||||
onSubmit={handleDialogSubmit}
|
onSubmit={handleDialogSubmit}
|
||||||
handleClose={closeKnownHostDialog}
|
handleClose={closeKnownHostDialog}
|
||||||
/>
|
/>
|
||||||
<Toast open={showCreateToast} onClose={() => setShowCreateToast(false)}>Host successfully created.</Toast>
|
<Toast open={showCreateToast} onClose={() => setShowCreateToast(false)}>{ t('KnownHosts.toast', { mode: 'created' }) }</Toast>
|
||||||
<Toast open={showDeleteToast} onClose={() => setShowDeleteToast(false)}>Host successfully deleted.</Toast>
|
<Toast open={showDeleteToast} onClose={() => setShowDeleteToast(false)}>{ t('KnownHosts.toast', { mode: 'deleted' }) }</Toast>
|
||||||
<Toast open={showEditToast} onClose={() => setShowEditToast(false)}>Host successfully edited.</Toast>
|
<Toast open={showEditToast} onClose={() => setShowEditToast(false)}>{ t('KnownHosts.toast', { mode: 'edited' }) }</Toast>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
6
webclient/src/containers/Initialize/Initialize.i18n.json
Normal file
6
webclient/src/containers/Initialize/Initialize.i18n.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"InitializeContainer": {
|
||||||
|
"title": "DID YOU KNOW",
|
||||||
|
"subtitle": "<1>Cockatrice is run by volunteers</1><1>that love card games!</1>"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useTranslation, Trans } from 'react-i18next';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Redirect, withRouter } from 'react-router-dom';
|
import { Redirect, withRouter } from 'react-router-dom';
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
@ -24,6 +25,7 @@ const useStyles = makeStyles(theme => ({
|
||||||
|
|
||||||
const Initialize = ({ initialized }: InitializeProps) => {
|
const Initialize = ({ initialized }: InitializeProps) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return initialized
|
return initialized
|
||||||
? <Redirect from="*" to={RouteEnum.LOGIN} />
|
? <Redirect from="*" to={RouteEnum.LOGIN} />
|
||||||
|
@ -31,9 +33,11 @@ const Initialize = ({ initialized }: InitializeProps) => {
|
||||||
<div className={'Initialize ' + classes.root}>
|
<div className={'Initialize ' + classes.root}>
|
||||||
<div className='Initialize-content'>
|
<div className='Initialize-content'>
|
||||||
<img src={Images.Logo} alt="logo" />
|
<img src={Images.Logo} alt="logo" />
|
||||||
<Typography variant="subtitle1" className='subtitle'>DID YOU KNOW</Typography>
|
<Typography variant="subtitle1" className='subtitle'>{ t('InitializeContainer.title') }</Typography>
|
||||||
<Typography variant="subtitle2">Cockatrice is run by volunteers</Typography>
|
<Trans i18nKey="InitializeContainer.subtitle">
|
||||||
<Typography variant="subtitle2">that love card games!</Typography>
|
<Typography variant="subtitle2"></Typography>
|
||||||
|
<Typography variant="subtitle2"></Typography>
|
||||||
|
</Trans>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="Initialize-graphics">
|
<div className="Initialize-graphics">
|
||||||
|
|
|
@ -1,6 +1,22 @@
|
||||||
{
|
{
|
||||||
"LoginContainer": {
|
"LoginContainer": {
|
||||||
"title": "Login",
|
"header": {
|
||||||
"subtitle": "A cross-platform virtual tabletop for multiplayer card games."
|
"title": "Login",
|
||||||
|
"subtitle": "A cross-platform virtual tabletop for multiplayer card games."
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"registerPrompt": "Not registered yet?",
|
||||||
|
"registerAction": "Create an account",
|
||||||
|
"credit": "Cockatrice is an open source project",
|
||||||
|
"version": "Version"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"subtitle1": "Play multiplayer card games online.",
|
||||||
|
"subtitle2": "Cross-platform virtual tabletop for multiplayer card games. Forever free."
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"passwordResetSuccessToast": "Password Reset Successfully",
|
||||||
|
"accountActivationSuccess": "Account Activated Successfully"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,8 +72,11 @@ const Login = ({ state, description, connectOptions }: LoginProps) => {
|
||||||
});
|
});
|
||||||
const [userToResetPassword, setUserToResetPassword] = useState(null);
|
const [userToResetPassword, setUserToResetPassword] = useState(null);
|
||||||
|
|
||||||
const passwordResetToast = useToast({ key: 'password-reset-success', children: 'Password Reset Successfully' })
|
const passwordResetToast = useToast({ key: 'password-reset-success', children: t('LoginContainer.toasts.passwordResetSuccess') });
|
||||||
const accountActivatedToast = useToast({ key: 'account-activation-success', children: 'Account Activated Successfully' })
|
const accountActivatedToast = useToast({
|
||||||
|
key: 'account-activation-success',
|
||||||
|
children: t('LoginContainer.toasts.accountActivationSuccess')
|
||||||
|
});
|
||||||
|
|
||||||
useReduxEffect(() => {
|
useReduxEffect(() => {
|
||||||
closeRequestPasswordResetDialog();
|
closeRequestPasswordResetDialog();
|
||||||
|
@ -227,8 +230,8 @@ const Login = ({ state, description, connectOptions }: LoginProps) => {
|
||||||
<img src={Images.Logo} alt="logo" />
|
<img src={Images.Logo} alt="logo" />
|
||||||
<span>COCKATRICE</span>
|
<span>COCKATRICE</span>
|
||||||
</div>
|
</div>
|
||||||
<Typography variant="h1">{ t('LoginContainer.title') }</Typography>
|
<Typography variant="h1">{ t('LoginContainer.header.title') }</Typography>
|
||||||
<Typography variant="subtitle1">{ t('LoginContainer.subtitle') }</Typography>
|
<Typography variant="subtitle1">{ t('LoginContainer.header.subtitle') }</Typography>
|
||||||
<div className="login-form">
|
<div className="login-form">
|
||||||
<LoginForm
|
<LoginForm
|
||||||
onSubmit={handleLogin}
|
onSubmit={handleLogin}
|
||||||
|
@ -247,17 +250,17 @@ const Login = ({ state, description, connectOptions }: LoginProps) => {
|
||||||
|
|
||||||
<div className="login-footer">
|
<div className="login-footer">
|
||||||
<div className="login-footer__register">
|
<div className="login-footer__register">
|
||||||
<span>Not registered yet?</span>
|
<span>{ t('LoginContainer.footer.registerPrompt') }</span>
|
||||||
<Button color="primary" onClick={openRegistrationDialog}>Create an account</Button>
|
<Button color="primary" onClick={openRegistrationDialog}>{ t('LoginContainer.footer.registerAction') }</Button>
|
||||||
</div>
|
</div>
|
||||||
<Typography variant="subtitle2">
|
<Typography variant="subtitle2">
|
||||||
Cockatrice is an open source project. { new Date().getUTCFullYear() }
|
{ t('LoginContainer.footer.credit') } - { new Date().getUTCFullYear() }
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{
|
{
|
||||||
serverProps.REACT_APP_VERSION && (
|
serverProps.REACT_APP_VERSION && (
|
||||||
<Typography variant="subtitle2">
|
<Typography variant="subtitle2">
|
||||||
Version: { serverProps.REACT_APP_VERSION }
|
{ t('LoginContainer.footer.version') }: { serverProps.REACT_APP_VERSION }
|
||||||
</Typography>
|
</Typography>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -298,10 +301,8 @@ const Login = ({ state, description, connectOptions }: LoginProps) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ /*<img src={loginGraphic} className="login-content__description-image"/>*/}
|
{ /*<img src={loginGraphic} className="login-content__description-image"/>*/}
|
||||||
<p className="login-content__description-subtitle1">Play multiplayer card games online.</p>
|
<p className="login-content__description-subtitle1">{ t('LoginContainer.content.subtitle1') }</p>
|
||||||
<p className="login-content__description-subtitle2">
|
<p className="login-content__description-subtitle2">{ t('LoginContainer.content.subtitle2') }</p>
|
||||||
Cross-platform virtual tabletop for multiplayer card games. Forever free.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"UnsupportedContainer": {
|
||||||
|
"title": "Unsupported Browser",
|
||||||
|
"subtitle1": "Please update your browser and/or check your permissions.",
|
||||||
|
"subtitle2": "Note: Private browsing causes some browsers to disable certain permissions or features."
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import Paper from '@material-ui/core/Paper';
|
import Paper from '@material-ui/core/Paper';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
@ -6,15 +7,17 @@ import Typography from '@material-ui/core/Typography';
|
||||||
import './Unsupported.css';
|
import './Unsupported.css';
|
||||||
|
|
||||||
const Unsupported = () => {
|
const Unsupported = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='Unsupported'>
|
<div className='Unsupported'>
|
||||||
<Paper className='Unsupported-paper'>
|
<Paper className='Unsupported-paper'>
|
||||||
<div className='Unsupported-paper__header'>
|
<div className='Unsupported-paper__header'>
|
||||||
<Typography variant="h1">Unsupported Browser</Typography>
|
<Typography variant="h1">{ t('UnsupportedContainer.title') }</Typography>
|
||||||
<Typography variant="subtitle1">Please update your browser and/or check your permissions.</Typography>
|
<Typography variant="subtitle1">{ t('UnsupportedContainer.subtitle1') }</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Typography variant="subtitle2">Note: Private browsing causes some browsers to disable certain permissions or features.</Typography>
|
<Typography variant="subtitle2">{ t('UnsupportedContainer.subtitle2') }</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"AccountActivationDialog": {
|
||||||
|
"title": "Account Activation",
|
||||||
|
"subtitle1": "Your account has not been activated yet.",
|
||||||
|
"subtitle2": "You need to provide the activation token received in the activation email."
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,12 +5,15 @@ import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
import CloseIcon from '@material-ui/icons/Close';
|
import CloseIcon from '@material-ui/icons/Close';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { AccountActivationForm } from 'forms';
|
import { AccountActivationForm } from 'forms';
|
||||||
|
|
||||||
import './AccountActivationDialog.css';
|
import './AccountActivationDialog.css';
|
||||||
|
|
||||||
const AccountActivationDialog = ({ classes, handleClose, isOpen, onSubmit }: any) => {
|
const AccountActivationDialog = ({ classes, handleClose, isOpen, onSubmit }: any) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleOnClose = () => {
|
const handleOnClose = () => {
|
||||||
handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
|
@ -18,7 +21,7 @@ const AccountActivationDialog = ({ classes, handleClose, isOpen, onSubmit }: any
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={handleOnClose} open={isOpen}>
|
<Dialog onClose={handleOnClose} open={isOpen}>
|
||||||
<DialogTitle disableTypography className="dialog-title">
|
<DialogTitle disableTypography className="dialog-title">
|
||||||
<Typography variant="h6">Account Activation</Typography>
|
<Typography variant="h6">{ t('AccountActivationDialog.title') }</Typography>
|
||||||
|
|
||||||
{handleOnClose ? (
|
{handleOnClose ? (
|
||||||
<IconButton onClick={handleOnClose}>
|
<IconButton onClick={handleOnClose}>
|
||||||
|
@ -28,8 +31,8 @@ const AccountActivationDialog = ({ classes, handleClose, isOpen, onSubmit }: any
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<Typography variant='subtitle1'>Your account has not been activated yet.</Typography>
|
<Typography variant='subtitle1'>{ t('AccountActivationDialog.subtitle1') }</Typography>
|
||||||
<Typography variant='subtitle1'>You need to provide the activation token received in the activation email.</Typography>
|
<Typography variant='subtitle1'>{ t('AccountActivationDialog.subtitle2') }</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AccountActivationForm onSubmit={onSubmit}></AccountActivationForm>
|
<AccountActivationForm onSubmit={onSubmit}></AccountActivationForm>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"KnownHostDialog": {
|
||||||
|
"title": "{mode, select, edit {Edit} other {Add}} Known Host",
|
||||||
|
"subtitle": "Adding a new host allows you to connect to different servers. Enter the details below to your host list."
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import { makeStyles } from '@material-ui/core/styles';
|
||||||
import AddIcon from '@material-ui/icons/Add';
|
import AddIcon from '@material-ui/icons/Add';
|
||||||
import CloseIcon from '@material-ui/icons/Close';
|
import CloseIcon from '@material-ui/icons/Close';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { KnownHostForm } from 'forms';
|
import { KnownHostForm } from 'forms';
|
||||||
|
|
||||||
|
@ -22,6 +23,9 @@ const useStyles = makeStyles(theme => ({
|
||||||
|
|
||||||
const KnownHostDialog = ({ handleClose, onRemove, onSubmit, isOpen, host }: any) => {
|
const KnownHostDialog = ({ handleClose, onRemove, onSubmit, isOpen, host }: any) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const mode = host ? 'edit' : 'add';
|
||||||
|
|
||||||
const handleOnClose = () => {
|
const handleOnClose = () => {
|
||||||
if (handleClose) {
|
if (handleClose) {
|
||||||
|
@ -33,7 +37,7 @@ const KnownHostDialog = ({ handleClose, onRemove, onSubmit, isOpen, host }: any)
|
||||||
<Dialog className={'KnownHostDialog ' + classes.root} onClose={handleOnClose} open={isOpen}>
|
<Dialog className={'KnownHostDialog ' + classes.root} onClose={handleOnClose} open={isOpen}>
|
||||||
<DialogTitle disableTypography className='dialog-title'>
|
<DialogTitle disableTypography className='dialog-title'>
|
||||||
<div className='dialog-title__wrapper'>
|
<div className='dialog-title__wrapper'>
|
||||||
<Typography variant='h2'>{ host ? 'Edit' : 'Add' } Known Host</Typography>
|
<Typography variant='h2'>{ t('KnownHostDialog.title', { mode }) }</Typography>
|
||||||
|
|
||||||
{handleClose ? (
|
{handleClose ? (
|
||||||
<IconButton onClick={handleClose}>
|
<IconButton onClick={handleClose}>
|
||||||
|
@ -44,7 +48,7 @@ const KnownHostDialog = ({ handleClose, onRemove, onSubmit, isOpen, host }: any)
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent className='dialog-content'>
|
<DialogContent className='dialog-content'>
|
||||||
<Typography className='dialog-content__subtitle' variant='subtitle1'>
|
<Typography className='dialog-content__subtitle' variant='subtitle1'>
|
||||||
Adding a new host allows you to connect to different servers. Enter the details below to your host list.
|
{ t('KnownHostDialog.subtitle') }
|
||||||
</Typography>
|
</Typography>
|
||||||
<KnownHostForm onRemove={onRemove} onSubmit={onSubmit} host={host}></KnownHostForm>
|
<KnownHostForm onRemove={onRemove} onSubmit={onSubmit} host={host}></KnownHostForm>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"RegistrationDialog": {
|
||||||
|
"title": "Create New Account"
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,12 +5,15 @@ import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
import CloseIcon from '@material-ui/icons/Close';
|
import CloseIcon from '@material-ui/icons/Close';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { RegisterForm } from 'forms';
|
import { RegisterForm } from 'forms';
|
||||||
|
|
||||||
import './RegistrationDialog.css';
|
import './RegistrationDialog.css';
|
||||||
|
|
||||||
const RegistrationDialog = ({ classes, handleClose, isOpen, onSubmit }: any) => {
|
const RegistrationDialog = ({ classes, handleClose, isOpen, onSubmit }: any) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleOnClose = () => {
|
const handleOnClose = () => {
|
||||||
handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
|
@ -18,7 +21,7 @@ const RegistrationDialog = ({ classes, handleClose, isOpen, onSubmit }: any) =>
|
||||||
return (
|
return (
|
||||||
<Dialog className="RegistrationDialog" onClose={handleOnClose} open={isOpen} maxWidth='xl'>
|
<Dialog className="RegistrationDialog" onClose={handleOnClose} open={isOpen} maxWidth='xl'>
|
||||||
<DialogTitle disableTypography className="dialog-title">
|
<DialogTitle disableTypography className="dialog-title">
|
||||||
<Typography variant="h6">Create New Account</Typography>
|
<Typography variant="h6">{ t('RegistrationDialog.title') }</Typography>
|
||||||
|
|
||||||
{handleOnClose ? (
|
{handleOnClose ? (
|
||||||
<IconButton onClick={handleOnClose}>
|
<IconButton onClick={handleOnClose}>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"RequestPasswordResetDialog": {
|
||||||
|
"title": "Request Password Reset"
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,12 +5,15 @@ import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
import CloseIcon from '@material-ui/icons/Close';
|
import CloseIcon from '@material-ui/icons/Close';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { RequestPasswordResetForm } from 'forms';
|
import { RequestPasswordResetForm } from 'forms';
|
||||||
|
|
||||||
import './RequestPasswordResetDialog.css';
|
import './RequestPasswordResetDialog.css';
|
||||||
|
|
||||||
const RequestPasswordResetDialog = ({ classes, handleClose, isOpen, onSubmit, skipTokenRequest }: any) => {
|
const RequestPasswordResetDialog = ({ classes, handleClose, isOpen, onSubmit, skipTokenRequest }: any) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleOnClose = () => {
|
const handleOnClose = () => {
|
||||||
handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
|
@ -18,7 +21,7 @@ const RequestPasswordResetDialog = ({ classes, handleClose, isOpen, onSubmit, sk
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={handleOnClose} open={isOpen}>
|
<Dialog onClose={handleOnClose} open={isOpen}>
|
||||||
<DialogTitle disableTypography className="dialog-title">
|
<DialogTitle disableTypography className="dialog-title">
|
||||||
<Typography variant="h6">Request Password Reset</Typography>
|
<Typography variant="h6">{ t('RequestPasswordResetDialog.title') }</Typography>
|
||||||
|
|
||||||
{handleOnClose ? (
|
{handleOnClose ? (
|
||||||
<IconButton onClick={handleOnClose}>
|
<IconButton onClick={handleOnClose}>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"ResetPasswordDialog": {
|
||||||
|
"title": "Reset Password"
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,12 +5,15 @@ import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
import CloseIcon from '@material-ui/icons/Close';
|
import CloseIcon from '@material-ui/icons/Close';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { ResetPasswordForm } from 'forms';
|
import { ResetPasswordForm } from 'forms';
|
||||||
|
|
||||||
import './ResetPasswordDialog.css';
|
import './ResetPasswordDialog.css';
|
||||||
|
|
||||||
const ResetPasswordDialog = ({ classes, handleClose, isOpen, onSubmit, userName }: any) => {
|
const ResetPasswordDialog = ({ classes, handleClose, isOpen, onSubmit, userName }: any) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleOnClose = () => {
|
const handleOnClose = () => {
|
||||||
handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
|
@ -18,7 +21,7 @@ const ResetPasswordDialog = ({ classes, handleClose, isOpen, onSubmit, userName
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={handleOnClose} open={isOpen}>
|
<Dialog onClose={handleOnClose} open={isOpen}>
|
||||||
<DialogTitle disableTypography className="dialog-title">
|
<DialogTitle disableTypography className="dialog-title">
|
||||||
<Typography variant="h6">Reset Password</Typography>
|
<Typography variant="h6">{t('ResetPasswordDialog.title')}</Typography>
|
||||||
|
|
||||||
{handleOnClose ? (
|
{handleOnClose ? (
|
||||||
<IconButton onClick={handleOnClose}>
|
<IconButton onClick={handleOnClose}>
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"AccountActivationForm": {
|
||||||
|
"error": {
|
||||||
|
"failed": "Account activation failed"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"activate": "Activate Account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import React, { useState } from "react";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Form, Field } from 'react-final-form';
|
import { Form, Field } from 'react-final-form';
|
||||||
import { OnChange } from 'react-final-form-listeners';
|
import { OnChange } from 'react-final-form-listeners';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
@ -16,6 +17,7 @@ import { ServerTypes } from 'store';
|
||||||
|
|
||||||
const AccountActivationForm = ({ onSubmit }) => {
|
const AccountActivationForm = ({ onSubmit }) => {
|
||||||
const [errorMessage, setErrorMessage] = useState(false);
|
const [errorMessage, setErrorMessage] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useReduxEffect(() => {
|
useReduxEffect(() => {
|
||||||
setErrorMessage(true);
|
setErrorMessage(true);
|
||||||
|
@ -33,7 +35,7 @@ const AccountActivationForm = ({ onSubmit }) => {
|
||||||
const errors: any = {};
|
const errors: any = {};
|
||||||
|
|
||||||
if (!values.token) {
|
if (!values.token) {
|
||||||
errors.token = 'Required';
|
errors.token = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
@ -45,17 +47,17 @@ const AccountActivationForm = ({ onSubmit }) => {
|
||||||
return (
|
return (
|
||||||
<form className="AccountActivationForm" onSubmit={handleSubmit}>
|
<form className="AccountActivationForm" onSubmit={handleSubmit}>
|
||||||
<div className="AccountActivationForm-item">
|
<div className="AccountActivationForm-item">
|
||||||
<Field label="Token" name="token" component={InputField} />
|
<Field label={t('Common.label.token')} name="token" component={InputField} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<div className="AccountActivationForm-error">
|
<div className="AccountActivationForm-error">
|
||||||
<Typography color="error">Account activation failed</Typography>
|
<Typography color="error">{ t('AccountActivationForm.error.failed') }</Typography>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button className="AccountActivationForm-submit rounded tall" color="primary" variant="contained" type="submit">
|
<Button className="AccountActivationForm-submit rounded tall" color="primary" variant="contained" type="submit">
|
||||||
Activate Account
|
{ t('AccountActivationForm.label.activate') }
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"KnownHostForm": {
|
||||||
|
"help": "Need help adding a new host?",
|
||||||
|
"label": {
|
||||||
|
"add": "Add Host",
|
||||||
|
"find": "Find Host"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Form, Field } from 'react-final-form'
|
import { Form, Field } from 'react-final-form'
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import AnchorLink from '@material-ui/core/Link';
|
import AnchorLink from '@material-ui/core/Link';
|
||||||
|
@ -12,20 +13,21 @@ import './KnownHostForm.css';
|
||||||
|
|
||||||
const KnownHostForm = ({ host, onRemove, onSubmit }) => {
|
const KnownHostForm = ({ host, onRemove, onSubmit }) => {
|
||||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const validate = values => {
|
const validate = values => {
|
||||||
const errors: any = {};
|
const errors: any = {};
|
||||||
|
|
||||||
if (!values.name) {
|
if (!values.name) {
|
||||||
errors.name = 'Required'
|
errors.name = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.host) {
|
if (!values.host) {
|
||||||
errors.host = 'Required'
|
errors.host = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.port) {
|
if (!values.port) {
|
||||||
errors.port = 'Required'
|
errors.port = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(errors).length) {
|
if (Object.keys(errors).length) {
|
||||||
|
@ -54,29 +56,29 @@ const KnownHostForm = ({ host, onRemove, onSubmit }) => {
|
||||||
{({ handleSubmit }) => (
|
{({ handleSubmit }) => (
|
||||||
<form className="KnownHostForm" onSubmit={handleSubmit}>
|
<form className="KnownHostForm" onSubmit={handleSubmit}>
|
||||||
<div className="KnownHostForm-item">
|
<div className="KnownHostForm-item">
|
||||||
<Field label="Host Name" name="name" component={InputField} />
|
<Field label={t('Common.label.hostName')} name="name" component={InputField} />
|
||||||
</div>
|
</div>
|
||||||
<div className="KnownHostForm-item">
|
<div className="KnownHostForm-item">
|
||||||
<Field label="Host Address" name="host" component={InputField} />
|
<Field label={t('Common.label.hostAddress')} name="host" component={InputField} />
|
||||||
</div>
|
</div>
|
||||||
<div className="KnownHostForm-item">
|
<div className="KnownHostForm-item">
|
||||||
<Field label="Port" name="port" type="number" component={InputField} />
|
<Field label={t('Common.label.port')} name="port" type="number" component={InputField} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button className="KnownHostForm-submit" color="primary" variant="contained" type="submit">
|
<Button className="KnownHostForm-submit" color="primary" variant="contained" type="submit">
|
||||||
{host ? 'Save Changes' : 'Add Host' }
|
{host ? t('Common.label.saveChanges') : t('KnownHostForm.label.add') }
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="KnownHostForm-actions">
|
<div className="KnownHostForm-actions">
|
||||||
<div className="KnownHostForm-actions__delete">
|
<div className="KnownHostForm-actions__delete">
|
||||||
{ host && (
|
{ host && (
|
||||||
<Button color="inherit" onClick={() => !confirmDelete ? setConfirmDelete(true) : onRemove(host)}>
|
<Button color="inherit" onClick={() => !confirmDelete ? setConfirmDelete(true) : onRemove(host)}>
|
||||||
{ !confirmDelete ? 'Delete' : 'Are you sure?' }
|
{ !confirmDelete ? t('Common.label.delete') : t('Common.label.confirmSure') }
|
||||||
</Button>
|
</Button>
|
||||||
) }
|
) }
|
||||||
</div>
|
</div>
|
||||||
<AnchorLink href='https://github.com/Cockatrice/Cockatrice/wiki/Public-Servers' target='_blank'>
|
<AnchorLink href='https://github.com/Cockatrice/Cockatrice/wiki/Public-Servers' target='_blank'>
|
||||||
Need help adding a new host?
|
{ t('KnownHostForm.label.find') }
|
||||||
</AnchorLink>
|
</AnchorLink>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
11
webclient/src/forms/LoginForm/LoginForm.i18n.json
Normal file
11
webclient/src/forms/LoginForm/LoginForm.i18n.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"LoginForm": {
|
||||||
|
"label": {
|
||||||
|
"autoConnect": "Auto Connect",
|
||||||
|
"forgot": "Forgot Password",
|
||||||
|
"login": "Login",
|
||||||
|
"savePassword": "Save Password",
|
||||||
|
"savedPassword": "Saved Password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { Form, Field } from 'react-final-form';
|
import { Form, Field } from 'react-final-form';
|
||||||
import { OnChange } from 'react-final-form-listeners';
|
import { OnChange } from 'react-final-form-listeners';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
|
|
||||||
|
@ -12,22 +13,24 @@ import { APP_USER } from 'types';
|
||||||
|
|
||||||
import './LoginForm.css';
|
import './LoginForm.css';
|
||||||
|
|
||||||
const PASSWORD_LABEL = 'Password';
|
|
||||||
const STORED_PASSWORD_LABEL = '* Saved Password *';
|
|
||||||
|
|
||||||
const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginFormProps) => {
|
const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginFormProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const PASSWORD_LABEL = t('Common.label.password');
|
||||||
|
const STORED_PASSWORD_LABEL = `* ${t('LoginForm.label.savedPassword')} *`;
|
||||||
|
|
||||||
const [host, setHost] = useState(null);
|
const [host, setHost] = useState(null);
|
||||||
const [passwordLabel, setPasswordLabel] = useState(PASSWORD_LABEL);
|
const [passwordLabel, setPasswordLabel] = useState(PASSWORD_LABEL);
|
||||||
const [autoConnect, setAutoConnect] = useAutoConnect();
|
const [autoConnect, setAutoConnect] = useAutoConnect();
|
||||||
|
|
||||||
|
|
||||||
const validate = values => {
|
const validate = values => {
|
||||||
const errors: any = {};
|
const errors: any = {};
|
||||||
|
|
||||||
if (!values.userName) {
|
if (!values.userName) {
|
||||||
errors.userName = 'Required';
|
errors.userName = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
if (!values.selectedHost) {
|
if (!values.selectedHost) {
|
||||||
errors.selectedHost = 'Required';
|
errors.selectedHost = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
@ -112,7 +115,7 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm
|
||||||
<form className='loginForm' onSubmit={handleSubmit}>
|
<form className='loginForm' onSubmit={handleSubmit}>
|
||||||
<div className='loginForm-items'>
|
<div className='loginForm-items'>
|
||||||
<div className='loginForm-item'>
|
<div className='loginForm-item'>
|
||||||
<Field label='Username' name='userName' component={InputField} autoComplete='username' />
|
<Field label={t('Common.label.username')} name='userName' component={InputField} autoComplete='username' />
|
||||||
<OnChange name="userName">{onUserNameChange}</OnChange>
|
<OnChange name="userName">{onUserNameChange}</OnChange>
|
||||||
</div>
|
</div>
|
||||||
<div className='loginForm-item'>
|
<div className='loginForm-item'>
|
||||||
|
@ -127,17 +130,19 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='loginForm-actions'>
|
<div className='loginForm-actions'>
|
||||||
<Field label='Save Password' name='remember' component={CheckboxField} />
|
<Field label={t('LoginForm.label.savePassword')} name='remember' component={CheckboxField} />
|
||||||
<OnChange name="remember">{onRememberChange}</OnChange>
|
<OnChange name="remember">{onRememberChange}</OnChange>
|
||||||
|
|
||||||
<Button color='primary' onClick={onResetPassword}>Forgot Password</Button>
|
<Button color='primary' onClick={onResetPassword}>
|
||||||
|
{ t('LoginForm.label.forgot') }
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className='loginForm-item'>
|
<div className='loginForm-item'>
|
||||||
<Field name='selectedHost' component={KnownHosts} />
|
<Field name='selectedHost' component={KnownHosts} />
|
||||||
<OnChange name="selectedHost">{setHost}</OnChange>
|
<OnChange name="selectedHost">{setHost}</OnChange>
|
||||||
</div>
|
</div>
|
||||||
<div className='loginForm-actions'>
|
<div className='loginForm-actions'>
|
||||||
<Field label='Auto Connect' name='autoConnect' component={CheckboxField} />
|
<Field label={t('LoginForm.label.autoConnect')} name='autoConnect' component={CheckboxField} />
|
||||||
<OnChange name="autoConnect">{onAutoConnectChange}</OnChange>
|
<OnChange name="autoConnect">{onAutoConnectChange}</OnChange>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -148,7 +153,7 @@ const LoginForm = ({ onSubmit, disableSubmitButton, onResetPassword }: LoginForm
|
||||||
type='submit'
|
type='submit'
|
||||||
disabled={disableSubmitButton}
|
disabled={disableSubmitButton}
|
||||||
>
|
>
|
||||||
Login
|
{ t('LoginForm.label.login') }
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
|
|
10
webclient/src/forms/RegisterForm/RegisterForm.i18n.json
Normal file
10
webclient/src/forms/RegisterForm/RegisterForm.i18n.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"RegisterForm": {
|
||||||
|
"label": {
|
||||||
|
"register": "Register"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"registerSuccess": "Registration Successful!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Form, Field } from 'react-final-form';
|
import { Form, Field } from 'react-final-form';
|
||||||
import { OnChange } from 'react-final-form-listeners';
|
import { OnChange } from 'react-final-form-listeners';
|
||||||
import setFieldTouched from 'final-form-set-field-touched'
|
import setFieldTouched from 'final-form-set-field-touched';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
@ -14,12 +15,13 @@ import './RegisterForm.css';
|
||||||
import { useToast } from 'components/Toast';
|
import { useToast } from 'components/Toast';
|
||||||
|
|
||||||
const RegisterForm = ({ onSubmit }: RegisterFormProps) => {
|
const RegisterForm = ({ onSubmit }: RegisterFormProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [emailRequired, setEmailRequired] = useState(false);
|
const [emailRequired, setEmailRequired] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [emailError, setEmailError] = useState(null);
|
const [emailError, setEmailError] = useState(null);
|
||||||
const [passwordError, setPasswordError] = useState(null);
|
const [passwordError, setPasswordError] = useState(null);
|
||||||
const [userNameError, setUserNameError] = useState(null);
|
const [userNameError, setUserNameError] = useState(null);
|
||||||
const { openToast } = useToast({ key: 'registration-success', children: 'Registration Successful!' })
|
const { openToast } = useToast({ key: 'registration-success', children: t('RegisterForm.toast.registerSuccess') })
|
||||||
|
|
||||||
const onHostChange = (host) => setEmailRequired(false);
|
const onHostChange = (host) => setEmailRequired(false);
|
||||||
const onEmailChange = () => emailError && setEmailError(null);
|
const onEmailChange = () => emailError && setEmailError(null);
|
||||||
|
@ -64,31 +66,31 @@ const RegisterForm = ({ onSubmit }: RegisterFormProps) => {
|
||||||
const errors: any = {};
|
const errors: any = {};
|
||||||
|
|
||||||
if (!values.userName) {
|
if (!values.userName) {
|
||||||
errors.userName = 'Required';
|
errors.userName = t('Common.validation.required');
|
||||||
} else if (userNameError) {
|
} else if (userNameError) {
|
||||||
errors.userName = userNameError;
|
errors.userName = userNameError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.password) {
|
if (!values.password) {
|
||||||
errors.password = 'Required';
|
errors.password = t('Common.validation.required');
|
||||||
} else if (values.password.length < 8) {
|
} else if (values.password.length < 8) {
|
||||||
errors.password = 'Minimum of 8 characters required';
|
errors.password = t('Common.validation.minChars', { count: 8 });
|
||||||
} else if (passwordError) {
|
} else if (passwordError) {
|
||||||
errors.password = passwordError;
|
errors.password = passwordError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.passwordConfirm) {
|
if (!values.passwordConfirm) {
|
||||||
errors.passwordConfirm = 'Required';
|
errors.passwordConfirm = t('Common.validation.required');
|
||||||
} else if (values.password !== values.passwordConfirm) {
|
} else if (values.password !== values.passwordConfirm) {
|
||||||
errors.passwordConfirm = 'Passwords don\'t match'
|
errors.passwordConfirm = t('Common.validation.passwordsMustMatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.selectedHost) {
|
if (!values.selectedHost) {
|
||||||
errors.selectedHost = 'Required';
|
errors.selectedHost = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (emailRequired && !values.email) {
|
if (emailRequired && !values.email) {
|
||||||
errors.email = 'Required';
|
errors.email = t('Common.validation.required');
|
||||||
} else if (emailError) {
|
} else if (emailError) {
|
||||||
errors.email = emailError;
|
errors.email = emailError;
|
||||||
}
|
}
|
||||||
|
@ -111,16 +113,22 @@ const RegisterForm = ({ onSubmit }: RegisterFormProps) => {
|
||||||
<form className="RegisterForm" onSubmit={handleSubmit}>
|
<form className="RegisterForm" onSubmit={handleSubmit}>
|
||||||
<div className="RegisterForm-column">
|
<div className="RegisterForm-column">
|
||||||
<div className="RegisterForm-item">
|
<div className="RegisterForm-item">
|
||||||
<Field label="Player Name" name="userName" component={InputField} autoComplete="username" />
|
<Field label={t('Common.label.username')} name="userName" component={InputField} autoComplete="username" />
|
||||||
<OnChange name="userName">{onUserNameChange}</OnChange>
|
<OnChange name="userName">{onUserNameChange}</OnChange>
|
||||||
</div>
|
</div>
|
||||||
<div className="RegisterForm-item">
|
<div className="RegisterForm-item">
|
||||||
<Field label="Password" name="password" type="password" component={InputField} autoComplete='new-password' />
|
<Field
|
||||||
|
label={t('Common.label.password')}
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
component={InputField}
|
||||||
|
autoComplete='new-password'
|
||||||
|
/>
|
||||||
<OnChange name="password">{onPasswordChange}</OnChange>
|
<OnChange name="password">{onPasswordChange}</OnChange>
|
||||||
</div>
|
</div>
|
||||||
<div className="RegisterForm-item">
|
<div className="RegisterForm-item">
|
||||||
<Field
|
<Field
|
||||||
label="Confirm Password"
|
label={t('Common.label.confirmPassword')}
|
||||||
name="passwordConfirm"
|
name="passwordConfirm"
|
||||||
type="password"
|
type="password"
|
||||||
component={InputField}
|
component={InputField}
|
||||||
|
@ -134,17 +142,17 @@ const RegisterForm = ({ onSubmit }: RegisterFormProps) => {
|
||||||
</div>
|
</div>
|
||||||
<div className="RegisterForm-column" >
|
<div className="RegisterForm-column" >
|
||||||
<div className="RegisterForm-item">
|
<div className="RegisterForm-item">
|
||||||
<Field label="Real Name" name="realName" component={InputField} />
|
<Field label={t('Common.label.realName')} name="realName" component={InputField} />
|
||||||
</div>
|
</div>
|
||||||
<div className="RegisterForm-item">
|
<div className="RegisterForm-item">
|
||||||
<Field label="Email" name="email" type="email" component={InputField} />
|
<Field label={t('Common.label.email')} name="email" type="email" component={InputField} />
|
||||||
<OnChange name="email">{onEmailChange}</OnChange>
|
<OnChange name="email">{onEmailChange}</OnChange>
|
||||||
</div>
|
</div>
|
||||||
<div className="RegisterForm-item">
|
<div className="RegisterForm-item">
|
||||||
<Field label="Country" name="country" component={CountryDropdown} />
|
<Field label={t('Common.label.country')} name="country" component={CountryDropdown} />
|
||||||
</div>
|
</div>
|
||||||
<Button className="RegisterForm-submit tall" color="primary" variant="contained" type="submit">
|
<Button className="RegisterForm-submit tall" color="primary" variant="contained" type="submit">
|
||||||
Register
|
{ t('RegisterForm.label.register') }
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"RequestPasswordResetForm": {
|
||||||
|
"error": "Request password reset failed",
|
||||||
|
"mfaEnabled": "Server has multi-factor authentication enabled",
|
||||||
|
"request": "Request Reset Token",
|
||||||
|
"skipRequest": "I already have a reset token"
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import React, { useState } from "react";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Form, Field } from 'react-final-form';
|
import { Form, Field } from 'react-final-form';
|
||||||
import { OnChange } from 'react-final-form-listeners';
|
import { OnChange } from 'react-final-form-listeners';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
@ -17,6 +18,7 @@ import { ServerTypes } from 'store';
|
||||||
const RequestPasswordResetForm = ({ onSubmit, skipTokenRequest }) => {
|
const RequestPasswordResetForm = ({ onSubmit, skipTokenRequest }) => {
|
||||||
const [errorMessage, setErrorMessage] = useState(false);
|
const [errorMessage, setErrorMessage] = useState(false);
|
||||||
const [isMFA, setIsMFA] = useState(false);
|
const [isMFA, setIsMFA] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useReduxEffect(() => {
|
useReduxEffect(() => {
|
||||||
setErrorMessage(true);
|
setErrorMessage(true);
|
||||||
|
@ -39,13 +41,13 @@ const RequestPasswordResetForm = ({ onSubmit, skipTokenRequest }) => {
|
||||||
const errors: any = {};
|
const errors: any = {};
|
||||||
|
|
||||||
if (!values.userName) {
|
if (!values.userName) {
|
||||||
errors.userName = 'Required';
|
errors.userName = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
if (isMFA && !values.email) {
|
if (isMFA && !values.email) {
|
||||||
errors.email = 'Required';
|
errors.email = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
if (!values.selectedHost) {
|
if (!values.selectedHost) {
|
||||||
errors.selectedHost = 'Required';
|
errors.selectedHost = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
@ -63,12 +65,12 @@ const RequestPasswordResetForm = ({ onSubmit, skipTokenRequest }) => {
|
||||||
<form className="RequestPasswordResetForm" onSubmit={handleSubmit}>
|
<form className="RequestPasswordResetForm" onSubmit={handleSubmit}>
|
||||||
<div className="RequestPasswordResetForm-items">
|
<div className="RequestPasswordResetForm-items">
|
||||||
<div className="RequestPasswordResetForm-item">
|
<div className="RequestPasswordResetForm-item">
|
||||||
<Field label="Username" name="userName" component={InputField} autoComplete="username" disabled={isMFA} />
|
<Field label={t('Common.label.username')} name="userName" component={InputField} autoComplete="username" disabled={isMFA} />
|
||||||
</div>
|
</div>
|
||||||
{isMFA ? (
|
{isMFA ? (
|
||||||
<div className="RequestPasswordResetForm-item">
|
<div className="RequestPasswordResetForm-item">
|
||||||
<Field label="Email" name="email" type="email" component={InputField} autoComplete="email" />
|
<Field label={t('Common.label.email')} name="email" type="email" component={InputField} autoComplete="email" />
|
||||||
<div>Server has multi-factor authentication enabled</div>
|
<div>{ t('RequestPasswordResetForm.mfaEnabled') }</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="RequestPasswordResetForm-item selectedHost">
|
<div className="RequestPasswordResetForm-item selectedHost">
|
||||||
|
@ -78,18 +80,18 @@ const RequestPasswordResetForm = ({ onSubmit, skipTokenRequest }) => {
|
||||||
|
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<div className="RequestPasswordResetForm-item">
|
<div className="RequestPasswordResetForm-item">
|
||||||
<Typography color="error">Request password reset failed</Typography>
|
<Typography color="error">{ t('RequestPasswordResetForm.error') }</Typography>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button className="RequestPasswordResetForm-submit rounded tall" color="primary" variant="contained" type="submit">
|
<Button className="RequestPasswordResetForm-submit rounded tall" color="primary" variant="contained" type="submit">
|
||||||
Request Reset Token
|
{ t('RequestPasswordResetForm.request') }
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Button color="primary" onClick={() => skipTokenRequest(form.getState().values.userName)}>
|
<Button color="primary" onClick={() => skipTokenRequest(form.getState().values.userName)}>
|
||||||
I already have a reset token
|
{ t('RequestPasswordResetForm.skipRequest') }
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"ResetPasswordForm": {
|
||||||
|
"error": "Password reset failed",
|
||||||
|
"label": {
|
||||||
|
"reset": "Reset Password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,8 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Form, Field } from 'react-final-form'
|
import { Form, Field } from 'react-final-form'
|
||||||
import { OnChange } from 'react-final-form-listeners'
|
import { OnChange } from 'react-final-form-listeners';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
@ -16,6 +17,7 @@ import { ServerTypes } from '../../store';
|
||||||
|
|
||||||
const ResetPasswordForm = ({ onSubmit, userName }) => {
|
const ResetPasswordForm = ({ onSubmit, userName }) => {
|
||||||
const [errorMessage, setErrorMessage] = useState(false);
|
const [errorMessage, setErrorMessage] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useReduxEffect(() => {
|
useReduxEffect(() => {
|
||||||
setErrorMessage(true);
|
setErrorMessage(true);
|
||||||
|
@ -25,25 +27,25 @@ const ResetPasswordForm = ({ onSubmit, userName }) => {
|
||||||
const errors: any = {};
|
const errors: any = {};
|
||||||
|
|
||||||
if (!values.userName) {
|
if (!values.userName) {
|
||||||
errors.userName = 'Required';
|
errors.userName = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
if (!values.token) {
|
if (!values.token) {
|
||||||
errors.token = 'Required';
|
errors.token = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.newPassword) {
|
if (!values.newPassword) {
|
||||||
errors.newPassword = 'Required';
|
errors.newPassword = t('Common.validation.required');
|
||||||
} else if (values.newPassword.length < 8) {
|
} else if (values.newPassword.length < 8) {
|
||||||
errors.newPassword = 'Minimum of 8 characters required';
|
errors.newPassword = t('Common.validation.minChars', { count: 8 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.passwordAgain) {
|
if (!values.passwordAgain) {
|
||||||
errors.passwordAgain = 'Required';
|
errors.passwordAgain = t('Common.validation.required');
|
||||||
} else if (values.newPassword !== values.passwordAgain) {
|
} else if (values.newPassword !== values.passwordAgain) {
|
||||||
errors.passwordAgain = 'Passwords don\'t match'
|
errors.passwordAgain = t('Common.validation.passwordsMustMatch');
|
||||||
}
|
}
|
||||||
if (!values.selectedHost) {
|
if (!values.selectedHost) {
|
||||||
errors.selectedHost = 'Required';
|
errors.selectedHost = t('Common.validation.required');
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
@ -62,16 +64,34 @@ const ResetPasswordForm = ({ onSubmit, userName }) => {
|
||||||
<form className='ResetPasswordForm' onSubmit={handleSubmit}>
|
<form className='ResetPasswordForm' onSubmit={handleSubmit}>
|
||||||
<div className='ResetPasswordForm-items'>
|
<div className='ResetPasswordForm-items'>
|
||||||
<div className='ResetPasswordForm-item'>
|
<div className='ResetPasswordForm-item'>
|
||||||
<Field label='Username' name='userName' component={InputField} autoComplete='username' disabled={!!userName} />
|
<Field
|
||||||
|
label={t('Common.label.username')}
|
||||||
|
name='userName'
|
||||||
|
component={InputField}
|
||||||
|
autoComplete='username'
|
||||||
|
disabled={!!userName}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='ResetPasswordForm-item'>
|
<div className='ResetPasswordForm-item'>
|
||||||
<Field label='Token' name='token' component={InputField} />
|
<Field label={t('Common.label.token')} name='token' component={InputField} />
|
||||||
</div>
|
</div>
|
||||||
<div className='ResetPasswordForm-item'>
|
<div className='ResetPasswordForm-item'>
|
||||||
<Field label='Password' name='newPassword' type='password' component={InputField} autoComplete='new-password' />
|
<Field
|
||||||
|
label={t('Common.label.password')}
|
||||||
|
name='newPassword'
|
||||||
|
type='password'
|
||||||
|
component={InputField}
|
||||||
|
autoComplete='new-password'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='ResetPasswordForm-item'>
|
<div className='ResetPasswordForm-item'>
|
||||||
<Field label='Password Again' name='passwordAgain' type='password' component={InputField} autoComplete='new-password' />
|
<Field
|
||||||
|
label={t('Common.label.passwordAgain')}
|
||||||
|
name='passwordAgain'
|
||||||
|
type='password'
|
||||||
|
component={InputField}
|
||||||
|
autoComplete='new-password'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='ResetPasswordForm-item'>
|
<div className='ResetPasswordForm-item'>
|
||||||
<Field name='selectedHost' component={KnownHosts} disabled />
|
<Field name='selectedHost' component={KnownHosts} disabled />
|
||||||
|
@ -79,12 +99,12 @@ const ResetPasswordForm = ({ onSubmit, userName }) => {
|
||||||
|
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<div className='ResetPasswordForm-item'>
|
<div className='ResetPasswordForm-item'>
|
||||||
<Typography color="error">Password reset failed</Typography>
|
<Typography color="error">{ t('ResetPasswordForm.error') }</Typography>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button className='ResetPasswordForm-submit rounded tall' color='primary' variant='contained' type='submit'>
|
<Button className='ResetPasswordForm-submit rounded tall' color='primary' variant='contained' type='submit'>
|
||||||
Reset Password
|
{ t('ResetPasswordForm.label.reset') }
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"Common":{"language":"Translate into English.","disconnect":"Disconnect"},"LoginContainer":{"title":"Login","subtitle":"A cross-platform virtual tabletop for multiplayer card games."}}
|
{"Common":{"language":"Translate into English.","disconnect":"Disconnect","label":{"confirmPassword":"Confirm Password","confirmSure":"Are you sure?","country":"Country","delete":"Delete","email":"Email","hostName":"Host Name","hostAddress":"Host Address","password":"Password","passwordAgain":"Password Again","port":"Port","realName":"Real Name","saveChanges":"Save Changes","token":"Token","username":"Username"},"validation":{"minChars":"Minimum of {count} {count, plural, one {character} other {characters}} required","passwordsMustMatch":"Passwords don't match","required":"Required"}},"KnownHosts":{"label":"Host","add":"Add new host","toast":"Host successfully {mode, select, created {created} deleted {deleted} other {edited}}."},"InitializeContainer":{"title":"DID YOU KNOW","subtitle":"<1>Cockatrice is run by volunteers</1><1>that love card games!</1>"},"LoginContainer":{"header":{"title":"Login","subtitle":"A cross-platform virtual tabletop for multiplayer card games."},"footer":{"registerPrompt":"Not registered yet?","registerAction":"Create an account","credit":"Cockatrice is an open source project","version":"Version"},"content":{"subtitle1":"Play multiplayer card games online.","subtitle2":"Cross-platform virtual tabletop for multiplayer card games. Forever free."},"toasts":{"passwordResetSuccessToast":"Password Reset Successfully","accountActivationSuccess":"Account Activated Successfully"}},"UnsupportedContainer":{"title":"Unsupported Browser","subtitle1":"Please update your browser and/or check your permissions.","subtitle2":"Note: Private browsing causes some browsers to disable certain permissions or features."},"AccountActivationDialog":{"title":"Account Activation","subtitle1":"Your account has not been activated yet.","subtitle2":"You need to provide the activation token received in the activation email."},"KnownHostDialog":{"title":"{mode, select, edit {Edit} other {Add}} Known Host","subtitle":"Adding a new host allows you to connect to different servers. Enter the details below to your host list."},"RegistrationDialog":{"title":"Create New Account"},"RequestPasswordResetDialog":{"title":"Request Password Reset"},"ResetPasswordDialog":{"title":"Reset Password"},"AccountActivationForm":{"error":{"failed":"Account activation failed"},"label":{"activate":"Activate Account"}},"KnownHostForm":{"help":"Need help adding a new host?","label":{"add":"Add Host","find":"Find Host"}},"LoginForm":{"label":{"autoConnect":"Auto Connect","forgot":"Forgot Password","login":"Login","savePassword":"Save Password","savedPassword":"Saved Password"}},"RegisterForm":{"label":{"register":"Register"},"toast":{"registerSuccess":"Registration Successful!"}},"RequestPasswordResetForm":{"error":"Request password reset failed","mfaEnabled":"Server has multi-factor authentication enabled","request":"Request Reset Token","skipRequest":"I already have a reset token"},"ResetPasswordForm":{"error":"Password reset failed","label":{"reset":"Reset Password"}}}
|
|
@ -1,5 +1,6 @@
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
import ICU from 'i18next-icu';
|
||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
|
||||||
import { Language } from 'types';
|
import { Language } from 'types';
|
||||||
|
@ -10,6 +11,7 @@ import I18nBackend from './i18n-backend';
|
||||||
import translation from './i18n-default.json';
|
import translation from './i18n-default.json';
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
|
.use(ICU)
|
||||||
.use(I18nBackend)
|
.use(I18nBackend)
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
|
@ -24,7 +26,7 @@ i18n
|
||||||
interpolation: {
|
interpolation: {
|
||||||
// not needed for react as it escapes by default
|
// not needed for react as it escapes by default
|
||||||
escapeValue: false,
|
escapeValue: false,
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
|
Loading…
Reference in a new issue