Extract i18n from shared UI components (#4834)

* Actionbar i18n

* ui Errors i18n

* Features i18n

* GapPriceSelector i18n

* WIP

* WIP #2

* Update methodDecoding

* ModalBox -> functional

* Signer pages i18n (missed previously)

* Update ModalBox tests

* Update variable
This commit is contained in:
Jaco Greeff 2017-03-10 12:04:40 +01:00 committed by GitHub
parent d98b7aab61
commit 4e5fd92e67
13 changed files with 452 additions and 199 deletions

View File

@ -15,11 +15,12 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import FileSaver from 'file-saver';
import FileDownloadIcon from 'material-ui/svg-icons/file/file-download';
import Button from '../../Button';
import { FileDownloadIcon } from '../../Icons';
class ActionbarExport extends Component {
static propTypes = {
@ -38,7 +39,12 @@ class ActionbarExport extends Component {
<Button
className={ className }
icon={ <FileDownloadIcon /> }
label='export'
label={
<FormattedMessage
id='ui.actionbar.export.button.export'
defaultMessage='export'
/>
}
onClick={ this.handleExport }
/>
);
@ -46,9 +52,7 @@ class ActionbarExport extends Component {
handleExport = () => {
const { filename, content } = this.props;
const text = JSON.stringify(content, null, 4);
const blob = new Blob([ text ], { type: 'application/json' });
FileSaver.saveAs(blob, `${filename}.json`);

View File

@ -15,10 +15,11 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import ActionSearch from 'material-ui/svg-icons/action/search';
import { FormattedMessage } from 'react-intl';
import Button from '../../Button';
import InputChip from '../../Form/InputChip';
import { SearchIcon } from '../../Icons';
import styles from './search.css';
@ -74,7 +75,12 @@ export default class ActionbarSearch extends Component {
<InputChip
addOnBlur
className={ styles.input }
hint='Enter search input...'
hint={
<FormattedMessage
id='ui.actionbar.search.hint'
defaultMessage='Enter search input...'
/>
}
ref='inputChip'
tokens={ tokens }
@ -86,7 +92,7 @@ export default class ActionbarSearch extends Component {
<Button
className={ styles.searchButton }
icon={ <ActionSearch /> }
icon={ <SearchIcon /> }
label=''
onClick={ this.handleSearchClick }
/>

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Snackbar } from 'material-ui';
@ -24,6 +25,19 @@ import { closeErrors } from './actions';
import styles from './errors.css';
const ERROR_REGEX = /-(\d+): (.+)$/;
const DURATION_OPEN = 60000;
const STYLE_BODY = {
height: 'auto',
whiteSpace: 'pre-line'
};
const STYLE_CONTENT = {
alignItems: 'center',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
lineHeight: '1.5em',
padding: '0.75em 0'
};
class Errors extends Component {
static propTypes = {
@ -46,23 +60,18 @@ class Errors extends Component {
<Snackbar
className={ styles.container }
open
action='close'
autoHideDuration={ 60000 }
action={
<FormattedMessage
id='ui.errors.close'
defaultMessage='close'
/>
}
autoHideDuration={ DURATION_OPEN }
message={ text }
onActionTouchTap={ onCloseErrors }
onRequestClose={ this.onRequestClose }
bodyStyle={ {
whiteSpace: 'pre-line',
height: 'auto'
} }
contentStyle={ {
display: 'flex',
flexDirection: 'row',
lineHeight: '1.5em',
padding: '0.75em 0',
alignItems: 'center',
justifyContent: 'space-between'
} }
bodyStyle={ STYLE_BODY }
contentStyle={ STYLE_CONTENT }
/>
);
}

View File

@ -14,6 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import { FormattedMessage } from 'react-intl';
const MODES = {
DEVELOPMENT: 1000, // only in dev mode, disabled by default, can be toggled
TESTING: 1011, // feature is available in dev mode
@ -28,13 +31,33 @@ const FEATURES = {
const DEFAULTS = {
[FEATURES.LANGUAGE]: {
mode: MODES.TESTING,
name: 'Language Selection',
description: 'Allows changing the default interface language'
name: (
<FormattedMessage
id='ui.features.defaults.i18n.name'
defaultMssage='Language Selection'
/>
),
description: (
<FormattedMessage
id='ui.features.defaults.i18n.desc'
defaultMssage='Allows changing the default interface language'
/>
)
},
[FEATURES.LOGLEVELS]: {
mode: MODES.TESTING,
name: 'Logging Level Selection',
description: 'Allows changing of the log levels for various components'
name: (
<FormattedMessage
id='ui.features.defaults.logging.name'
defaultMssage='Logging Level Selection'
/>
),
description: (
<FormattedMessage
id='ui.features.defaults.logging.desc'
defaultMssage='Allows changing of the log levels for various components'
/>
)
}
};

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
export default class CustomTooltip extends Component {
static propTypes = {
@ -41,11 +42,15 @@ export default class CustomTooltip extends Component {
return (
<div>
<p className='label'>
{ count.toNumber() } transactions
with gas price set from
<span> { minprice.toFormat(0) } </span>
to
<span> { maxprice.toFormat(0) } </span>
<FormattedMessage
id='ui.gasPriceSelector.customTooltip.transactions'
defaultMessage='{number} {number, plural, one {transaction} other {transactions}} with gas price set from {minPrice} to {maxPrice}'
values={ {
number: count.toNumber(),
minPrice: <span>{ minprice.toFormat(0) }</span>,
maxPrice: <span>{ maxprice.toFormat(0) }</span>
} }
/>
</p>
</div>
);

View File

@ -34,6 +34,7 @@ export DoneIcon from 'material-ui/svg-icons/action/done-all';
export DialIcon from 'material-ui/svg-icons/communication/dialpad';
export EditIcon from 'material-ui/svg-icons/content/create';
export ErrorIcon from 'material-ui/svg-icons/alert/error';
export FileDownloadIcon from 'material-ui/svg-icons/file/file-download';
export FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
export FileIcon from 'material-ui/svg-icons/editor/insert-drive-file';
export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint';
@ -55,6 +56,7 @@ export RefreshIcon from 'material-ui/svg-icons/action/autorenew';
export ReorderIcon from 'material-ui/svg-icons/action/reorder';
export ReplayIcon from 'material-ui/svg-icons/av/replay';
export SaveIcon from 'material-ui/svg-icons/content/save';
export SearchIcon from 'material-ui/svg-icons/action/search';
export SendIcon from 'material-ui/svg-icons/content/send';
export SettingsIcon from 'material-ui/svg-icons/action/settings';
export SnoozeIcon from 'material-ui/svg-icons/av/snooze';

View File

@ -17,6 +17,7 @@
import { CircularProgress } from 'material-ui';
import moment from 'moment';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { TypedInput, InputAddress } from '../Form';
@ -112,28 +113,50 @@ class MethodDecoding extends Component {
return null;
}
const gasValue = gas.mul(gasPrice);
const gasProvided = (
<span className={ styles.highlight }>
<FormattedMessage
id='ui.methodDecoding.gasValues'
defaultMessage='{gas} gas ({gasPrice}M/{tag})'
values={ {
gas: gas.toFormat(0),
gasPrice: gasPrice.div(1000000).toFormat(0),
tag: <small>ETH</small>
} }
/>
</span>
);
const gasProvidedEth = (
<span className={ styles.highlight }>
{ this.renderEtherValue(gas.mul(gasPrice)) }
</span>
);
const gasUsed = transaction.gasUsed
? (
<span className={ styles.highlight }>
<FormattedMessage
id='ui.methodDecoding.gasUsed'
defaultMessage=' ({gas} gas used)'
values={ {
gas: transaction.gasUsed.toFormat(0)
} }
/>
</span>
)
: '';
return (
<div className={ styles.gasDetails }>
<span>{ historic ? 'Provided' : 'Provides' } </span>
<span className={ styles.highlight }>
{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>)
</span>
{
transaction.gasUsed
? (
<span>
<span>used</span>
<span className={ styles.highlight }>
{ transaction.gasUsed.toFormat(0) } gas
</span>
</span>
)
: null
}
<span> for a total transaction value of </span>
<span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
<FormattedMessage
id='ui.methodDecoding.txValues'
defaultMessage='{historic, select, true {Provided} false {Provides}} {gasProvided}{gasUsed} for a total transaction value of {gasProvidedEth}'
values={ {
historic,
gasProvided,
gasProvidedEth,
gasUsed
} }
/>
{ this.renderMinBlock() }
</div>
);
@ -148,14 +171,40 @@ class MethodDecoding extends Component {
}
if (condition.block && condition.block.gt(0)) {
const blockNumber = (
<span className={ styles.highlight }>
#{ condition.block.toFormat(0) }
</span>
);
return (
<span>, { historic ? 'Submitted' : 'Submission' } at block <span className={ styles.highlight }>#{ condition.block.toFormat(0) }</span></span>
<FormattedMessage
id='ui.methodDecoding.condition.block'
defaultMessage=', {historic, select, true {Submitted} false {Submission}} at block {blockNumber}'
values={ {
historic,
blockNumber
} }
/>
);
}
if (condition.time) {
const timestamp = (
<span className={ styles.highlight }>
{ moment(condition.time).format('LLLL') }
</span>
);
return (
<span>, { historic ? 'Submitted' : 'Submission' } at <span className={ styles.highlight }>{ moment(condition.time).format('LLLL') }</span></span>
<FormattedMessage
id='ui.methodDecoding.condition.time'
defaultMessage=', {historic, select, true {Submitted} false {Submission}} at {timestamp}'
values={ {
historic,
timestamp
} }
/>
);
}
@ -190,9 +239,12 @@ class MethodDecoding extends Component {
getAscii () {
const { api } = this.context;
const { transaction } = this.props;
const ascii = api.util.hexToAscii(transaction.input || transaction.data);
const value = api.util.hexToAscii(transaction.input || transaction.data);
return { value: ascii, valid: ASCII_INPUT.test(ascii) };
return {
value,
valid: ASCII_INPUT.test(value)
};
}
renderInputValue () {
@ -218,24 +270,49 @@ class MethodDecoding extends Component {
? text
: text.slice(0, 50) + '...';
const inputDesc = (
<span
onClick={ this.toggleInputType }
className={ [ styles.clickable, styles.noSelect ].join(' ') }
>
{
type === 'ascii'
? (
<FormattedMessage
id='ui.methodDecoding.input.input'
defaultMessage='input'
/>
)
: (
<FormattedMessage
id='ui.methodDecoding.input.data'
defaultMessage='data'
/>
)
}
</span>
);
const inputValue = (
<span
onClick={ this.toggleInputExpand }
className={ expandable ? styles.clickable : '' }
>
<code className={ styles.inputData }>
{ textToShow }
</code>
</span>
);
return (
<div className={ styles.details }>
<span>with the </span>
<span
onClick={ this.toggleInputType }
className={ [ styles.clickable, styles.noSelect ].join(' ') }
>
{ type === 'ascii' ? 'input' : 'data' }
</span>
<span> &nbsp; </span>
<span
onClick={ this.toggleInputExpand }
className={ expandable ? styles.clickable : '' }
>
<code className={ styles.inputData }>
{ textToShow }
</code>
</span>
<FormattedMessage
id='ui.methodDecoding.input.withInput'
defaultMessage='with the {inputDesc} {inputValue}'
values={ {
inputDesc,
inputValue
} }
/>
</div>
);
}
@ -251,15 +328,19 @@ class MethodDecoding extends Component {
default:
return (
<div className={ styles.details }>
<div>
<span>{ historic ? 'Transferred' : 'Will transfer' } </span>
<span className={ styles.highlight }>
{ this.renderTokenValue(value.value) }
</span>
<span> to </span>
</div>
{ this.renderAddressName(address) }
<FormattedMessage
id='ui.methodDecoding.token.transfer'
defaultMessage='{historic, select, true {Transferred} false {Will transfer}} {value} to {address}'
values={ {
historic,
value: (
<span className={ styles.highlight }>
{ this.renderTokenValue(value.value) }
</span>
),
address: this.renderAddressName(address)
} }
/>
</div>
);
}
@ -272,7 +353,10 @@ class MethodDecoding extends Component {
if (!historic) {
return (
<div className={ styles.details }>
Will deploy a contract.
<FormattedMessage
id='ui.methodDecoding.deploy.willDeploy'
defaultMessage='Will deploy a contract'
/>
</div>
);
}
@ -280,15 +364,24 @@ class MethodDecoding extends Component {
return (
<div className={ styles.details }>
<div>
<span>Deployed a contract at address </span>
<FormattedMessage
id='ui.methodDecoding.deploy.address'
defaultMessage='Deployed a contract at address '
/>
</div>
{ this.renderAddressName(transaction.creates, false) }
<div>
{ methodInputs && methodInputs.length ? 'with the following parameters:' : ''}
{
methodInputs && methodInputs.length
? (
<FormattedMessage
id='ui.methodDecoding.deploy.params'
defaultMessage='with the following parameters:'
/>
)
: ''
}
</div>
<div className={ styles.inputs }>
{ this.renderInputs() }
</div>
@ -300,17 +393,32 @@ class MethodDecoding extends Component {
const { historic, transaction } = this.props;
const { isContract } = this.state;
const valueEth = (
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
);
const aContract = isContract
? (
<FormattedMessage
id='ui.methodDecoding.receive.contract'
defaultMessage='the contract '
/>
)
: '';
return (
<div className={ styles.details }>
<div>
<span>{ historic ? 'Received' : 'Will receive' } </span>
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
<span> from { isContract ? 'the contract' : '' } </span>
</div>
{ this.renderAddressName(transaction.from) }
<FormattedMessage
id='ui.methodDecoding.receive.info'
defaultMessage='{historic, select, true {Received} false {Will receive}} {valueEth} from {aContract}{address}'
values={ {
historic,
valueEth,
aContract,
address: this.renderAddressName(transaction.from)
} }
/>
{ this.renderInputValue() }
</div>
);
@ -320,17 +428,32 @@ class MethodDecoding extends Component {
const { historic, transaction } = this.props;
const { isContract } = this.state;
const valueEth = (
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
);
const aContract = isContract
? (
<FormattedMessage
id='ui.methodDecoding.transfer.contract'
defaultMessage='the contract '
/>
)
: '';
return (
<div className={ styles.details }>
<div>
<span>{ historic ? 'Transferred' : 'Will transfer' } </span>
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
<span> to { isContract ? 'the contract' : '' } </span>
</div>
{ this.renderAddressName(transaction.to) }
<FormattedMessage
id='ui.methodDecoding.transfer.info'
defaultMessage='{historic, select, true {Transferred} false {Will transfer}} {valueEth} to {aContract}{address}'
values={ {
historic,
valueEth,
aContract,
address: this.renderAddressName(transaction.to)
} }
/>
{ this.renderInputValue() }
</div>
);
@ -340,26 +463,31 @@ class MethodDecoding extends Component {
const { historic, transaction } = this.props;
const { methodName, methodInputs } = this.state;
const method = (
<span className={ styles.name }>
{ methodName }
</span>
);
const ethValue = (
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
);
return (
<div className={ styles.details }>
<div className={ styles.description }>
<div>
<span>{ historic ? 'Executed' : 'Will execute' } the </span>
<span className={ styles.name }>{ methodName }</span>
<span> function on the contract </span>
</div>
{ this.renderAddressName(transaction.to) }
<div>
<span>transferring </span>
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
<span>
{ methodInputs.length ? ', passing the following parameters:' : '.' }
</span>
</div>
<FormattedMessage
id='ui.methodDecoding.signature.info'
defaultMessage='{historic, select, true {Executed} false {Will execute}} the {method} function on the contract {address} trsansferring {ethValue}{inputLength, plural, zero {,} other {passing the following {inputLength, plural, one {parameter} other {parameters}}}}'
values={ {
historic,
method,
ethValue,
address: this.renderAddressName(transaction.to),
inputLength: methodInputs.length
} }
/>
</div>
<div className={ styles.inputs }>
{ this.renderInputs() }
@ -371,23 +499,29 @@ class MethodDecoding extends Component {
renderUnknownMethod () {
const { historic, transaction } = this.props;
const method = (
<span className={ styles.name }>
an unknown/unregistered
</span>
);
const ethValue = (
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
);
return (
<div className={ styles.details }>
<div>
<span>{ historic ? 'Executed' : 'Will execute' } </span>
<span className={ styles.name }>an unknown/unregistered</span>
<span> method on the contract </span>
</div>
{ this.renderAddressName(transaction.to) }
<div>
<span>transferring </span>
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
<span>.</span>
</div>
<FormattedMessage
id='ui.methodDecoding.unknown.info'
defaultMessage='{historic, select, true {Executed} false {Will execute}} the {method} on the contract {address} transferring {ethValue}.'
values={ {
historic,
method,
ethValue,
address: this.renderAddressName(transaction.to)
} }
/>
</div>
);
}

View File

@ -0,0 +1,35 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { PropTypes } from 'react';
import styles from './modalBox.css';
export default function Body ({ children }) {
if (!children) {
return null;
}
return (
<div className={ styles.body }>
{ children }
</div>
);
}
Body.propTypes = {
children: PropTypes.node
};

View File

@ -14,60 +14,30 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import React, { PropTypes } from 'react';
import { nodeOrStringProptype } from '~/util/proptypes';
import Body from './body';
import Summary from './summary';
import styles from './modalBox.css';
export default class ModalBox extends Component {
static propTypes = {
children: PropTypes.node,
icon: PropTypes.node.isRequired,
summary: nodeOrStringProptype()
}
render () {
const { icon } = this.props;
return (
<div className={ styles.body }>
<div className={ styles.icon }>
{ icon }
</div>
<div className={ styles.content }>
{ this.renderSummary() }
{ this.renderBody() }
</div>
export default function ModalBox ({ children, icon, summary }) {
return (
<div className={ styles.body }>
<div className={ styles.icon }>
{ icon }
</div>
);
}
renderBody () {
const { children } = this.props;
if (!children) {
return null;
}
return (
<div className={ styles.body }>
{ children }
<div className={ styles.content }>
<Summary summary={ summary } />
<Body children={ children } />
</div>
);
}
renderSummary () {
const { summary } = this.props;
if (!summary) {
return null;
}
return (
<div className={ styles.summary }>
{ summary }
</div>
);
}
</div>
);
}
ModalBox.propTypes = {
children: PropTypes.node,
icon: PropTypes.node.isRequired,
summary: nodeOrStringProptype()
};

View File

@ -21,12 +21,16 @@ import ModalBox from './';
let component;
const CHILDREN = <div id='testChild'>testChild</div>;
const ICON = <div id='testIcon'>testIcon</div>;
const SUMMARY = <div id='testSummary'>testSummary</div>;
function render () {
component = shallow(
<ModalBox
children={ <div id='testChild'>testChild</div> }
icon={ <div id='testIcon'>testIcon</div> }
summary={ <div id='testSummary'>testSummary</div> }
children={ CHILDREN }
icon={ ICON }
summary={ SUMMARY }
/>
);
@ -42,15 +46,17 @@ describe('ui/ModalBox', () => {
expect(component).to.be.ok;
});
it('adds the children as supplied', () => {
expect(component.find('#testChild').text()).to.equal('testChild');
});
it('adds the icon as supplied', () => {
expect(component.find('#testIcon').text()).to.equal('testIcon');
});
it('adds the summary as supplied', () => {
expect(component.find('#testSummary').text()).to.equal('testSummary');
describe('components', () => {
it('adds the Body as supplied', () => {
expect(component.find('Body').props().children).to.deep.equal(CHILDREN);
});
it('adds the Summary as supplied', () => {
expect(component.find('Summary').props().summary).to.deep.equal(SUMMARY);
});
});
});

View File

@ -0,0 +1,37 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import { nodeOrStringProptype } from '~/util/proptypes';
import styles from './modalBox.css';
export default function Summary ({ summary }) {
if (!summary) {
return null;
}
return (
<div className={ styles.summary }>
{ summary }
</div>
);
}
Summary.propTypes = {
summary: nodeOrStringProptype()
};

View File

@ -16,6 +16,7 @@
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
@ -65,7 +66,10 @@ class Embedded extends Component {
if (!pending.length) {
return (
<div className={ styles.none }>
There are currently no pending requests awaiting your confirmation
<FormattedMessage
id='signer.embedded.noPending'
defaultMessage='There are currently no pending requests awaiting your confirmation'
/>
</div>
);
}

View File

@ -15,10 +15,11 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Store from '../../store';
import * as RequestsActions from '~/redux/providers/signerActions';
@ -74,7 +75,14 @@ class RequestsPage extends Component {
}
return (
<Container title='Local Transactions'>
<Container
title={
<FormattedMessage
id='signer.requestsPage.queueTitle'
defaultMessage='Local Transactions'
/>
}
>
<TxList
address=''
hashes={ localHashes }
@ -90,7 +98,10 @@ class RequestsPage extends Component {
return (
<Container>
<div className={ styles.noRequestsMsg }>
There are no requests requiring your confirmation.
<FormattedMessage
id='signer.requestsPage.noPending'
defaultMessage='There are no requests requiring your confirmation.'
/>
</div>
</Container>
);
@ -99,7 +110,14 @@ class RequestsPage extends Component {
const items = pending.sort(this._sortRequests).map(this.renderPending);
return (
<Container title='Pending Requests'>
<Container
title={
<FormattedMessage
id='signer.requestsPage.pendingTitle'
defaultMessage='Pending Requests'
/>
}
>
{ items }
</Container>
);