l10n support for xrp validation

pull/539/head
slowbackspace 5 years ago
parent 8d1c01bc61
commit 934df38064

@ -6,6 +6,8 @@ import { findDevice, getPendingAmount } from 'reducers/utils';
import { toDecimalAmount } from 'utils/formatUtils'; import { toDecimalAmount } from 'utils/formatUtils';
import { toFiatCurrency } from 'utils/fiatConverter'; import { toFiatCurrency } from 'utils/fiatConverter';
import * as validators from 'utils/validators'; import * as validators from 'utils/validators';
import l10nMessages from 'views/Wallet/views/Account/Send/validation.messages';
import l10nCommonMessages from 'views/common.messages';
import type { import type {
Dispatch, Dispatch,
@ -150,11 +152,11 @@ const addressValidation = ($state: State): PayloadAction<State> => (
const { address } = state; const { address } = state;
if (address.length < 1) { if (address.length < 1) {
state.errors.address = 'Address is not set'; state.errors.address = l10nMessages.TR_ADDRESS_IS_NOT_SET;
} else if (!AddressValidator.validate(address, 'XRP')) { } else if (!AddressValidator.validate(address, 'XRP')) {
state.errors.address = 'Address is not valid'; state.errors.address = l10nMessages.TR_ADDRESS_IS_NOT_VALID;
} else if (address.toLowerCase() === account.descriptor.toLowerCase()) { } else if (address.toLowerCase() === account.descriptor.toLowerCase()) {
state.errors.address = 'Cannot send to myself'; state.errors.address = l10nMessages.TR_CANNOT_SEND_TO_MYSELF;
} }
return state; return state;
}; };
@ -226,9 +228,13 @@ const addressLabel = ($state: State): PayloadAction<State> => (
currentNetworkAccount.deviceState currentNetworkAccount.deviceState
); );
if (device) { if (device) {
state.infos.address = `${ state.infos.address = {
device.instanceLabel ...l10nCommonMessages.TR_DEVICE_LABEL_ACCOUNT_HASH,
} Account #${currentNetworkAccount.index + 1}`; values: {
deviceLabel: device.instanceLabel,
number: currentNetworkAccount.index + 1,
},
};
} }
} else { } else {
// corner-case: the same derivation path is used on different networks // corner-case: the same derivation path is used on different networks
@ -241,11 +247,14 @@ const addressLabel = ($state: State): PayloadAction<State> => (
const { networks } = getState().localStorage.config; const { networks } = getState().localStorage.config;
const otherNetwork = networks.find(c => c.shortcut === otherNetworkAccount.network); const otherNetwork = networks.find(c => c.shortcut === otherNetworkAccount.network);
if (device && otherNetwork) { if (device && otherNetwork) {
state.warnings.address = `Looks like it's ${ state.warnings.address = {
device.instanceLabel ...l10nCommonMessages.TR_LOOKS_LIKE_IT_IS_DEVICE_LABEL,
} Account #${otherNetworkAccount.index + 1} address of ${ values: {
otherNetwork.name deviceLabel: device.instanceLabel,
} network`; number: otherNetworkAccount.index + 1,
network: otherNetwork.name,
},
};
} }
} }
} }
@ -268,19 +277,22 @@ const amountValidation = ($state: State): PayloadAction<State> => (
const { amount } = state; const { amount } = state;
if (amount.length < 1) { if (amount.length < 1) {
state.errors.amount = 'Amount is not set'; state.errors.amount = l10nMessages.TR_AMOUNT_IS_NOT_SET;
} else if (amount.length > 0 && !validators.isNumber(amount)) { } else if (amount.length > 0 && !validators.isNumber(amount)) {
state.errors.amount = 'Amount is not a number'; state.errors.amount = l10nMessages.TR_AMOUNT_IS_NOT_A_NUMBER;
} else { } else {
const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol); const pendingAmount: BigNumber = getPendingAmount(pending, state.networkSymbol);
if (!validators.hasDecimals(state.amount, 6)) { if (!validators.hasDecimals(state.amount, 6)) {
state.errors.amount = 'Maximum 6 decimals allowed'; state.errors.amount = {
...l10nMessages.TR_MAXIMUM_DECIMALS_ALLOWED,
values: { decimals: 6 },
};
} else if ( } else if (
new BigNumber(state.total).isGreaterThan( new BigNumber(state.total).isGreaterThan(
new BigNumber(account.balance).minus(pendingAmount) new BigNumber(account.balance).minus(pendingAmount)
) )
) { ) {
state.errors.amount = 'Not enough funds'; state.errors.amount = l10nMessages.TR_NOT_ENOUGH_FUNDS;
} }
} }
@ -288,15 +300,23 @@ const amountValidation = ($state: State): PayloadAction<State> => (
!state.errors.amount && !state.errors.amount &&
new BigNumber(account.balance).minus(state.total).lt(account.reserve) new BigNumber(account.balance).minus(state.total).lt(account.reserve)
) { ) {
state.errors.amount = `Not enough funds. Reserved amount for this account is ${ state.errors.amount = {
account.reserve ...l10nMessages.TR_NOT_ENOUGH_FUNDS_RESERVED_AMOUNT,
} ${state.networkSymbol}`; values: {
reservedAmount: account.reserve,
networkSymbol: state.networkSymbol,
},
};
} }
if (!state.errors.amount && new BigNumber(state.amount).lt(state.minAmount)) { if (!state.errors.amount && new BigNumber(state.amount).lt(state.minAmount)) {
state.errors.amount = `Amount is too low. Minimum amount for creating a new account is ${ state.errors.amount = {
state.minAmount ...l10nMessages.TR_AMOUNT_IS_TOO_LOW_MINIMUM_AMOUNT_FOR_CREATING,
} ${state.networkSymbol}`; values: {
reservedAmount: state.minAmount,
networkSymbol: state.networkSymbol,
},
};
} }
return state; return state;
@ -317,15 +337,15 @@ export const feeValidation = ($state: State): PayloadAction<State> => (
const { fee } = state; const { fee } = state;
if (fee.length < 1) { if (fee.length < 1) {
state.errors.fee = 'Fee is not set'; state.errors.fee = l10nMessages.TR_FEE_IS_NOT_SET;
} else if (fee.length > 0 && !validators.isAbs(fee)) { } else if (fee.length > 0 && !validators.isAbs(fee)) {
state.errors.fee = 'Fee must be an absolute number'; state.errors.fee = l10nMessages.TR_FEE_MUST_ME_AN_ABSOLUT_NUMBER;
} else { } else {
const gl: BigNumber = new BigNumber(fee); const gl: BigNumber = new BigNumber(fee);
if (gl.isLessThan(network.fee.minFee)) { if (gl.isLessThan(network.fee.minFee)) {
state.errors.fee = 'Fee is below recommended'; state.errors.fee = l10nMessages.TR_FEE_IS_BELOW_RECOMMENDED;
} else if (gl.isGreaterThan(network.fee.maxFee)) { } else if (gl.isGreaterThan(network.fee.maxFee)) {
state.errors.fee = 'Fee is above recommended'; state.errors.fee = l10nMessages.TR_FEE_IS_ABOVE_RECOMMENDED;
} }
} }
return state; return state;
@ -340,11 +360,11 @@ export const destinationTagValidation = ($state: State): PayloadAction<State> =>
const { destinationTag } = state; const { destinationTag } = state;
if (destinationTag.length > 0 && !validators.isAbs(destinationTag)) { if (destinationTag.length > 0 && !validators.isAbs(destinationTag)) {
state.errors.destinationTag = 'Destination tag must be an absolute number'; state.errors.destinationTag = l10nMessages.TR_DESTINATION_TAG_MUST_BE_AN_ABSOLUTE;
} }
if (parseInt(destinationTag, 10) > U_INT_32) { if (parseInt(destinationTag, 10) > U_INT_32) {
state.errors.destinationTag = 'Number is too big'; state.errors.destinationTag = l10nMessages.TR_DESTINATION_TAG_IS_NOT_VALID;
} }
return state; return state;
@ -386,9 +406,16 @@ export const getFeeLevels = (
const { network } = getState().selectedAccount; const { network } = getState().selectedAccount;
if (!network) return []; // flowtype fallback if (!network) return []; // flowtype fallback
const l10nFeeMap = {
Low: l10nCommonMessages.TR_LOW_FEE,
Normal: l10nCommonMessages.TR_NORMAL_FEE,
High: l10nCommonMessages.TR_HIGH_FEE,
};
// map BlockchainFeeLevel to SendFormReducer FeeLevel // map BlockchainFeeLevel to SendFormReducer FeeLevel
const levels = feeLevels.map(level => ({ const levels = feeLevels.map(level => ({
value: level.name, value: level.name,
localizedValue: l10nFeeMap[level.value] || level.value,
fee: level.value, fee: level.value,
label: `${toDecimalAmount(level.value, network.decimals)} ${network.symbol}`, label: `${toDecimalAmount(level.value, network.decimals)} ${network.symbol}`,
})); }));
@ -398,11 +425,13 @@ export const getFeeLevels = (
selected && selected.value === 'Custom' selected && selected.value === 'Custom'
? { ? {
value: 'Custom', value: 'Custom',
localizedValue: l10nCommonMessages.TR_CUSTOM_FEE,
fee: selected.fee, fee: selected.fee,
label: `${toDecimalAmount(selected.fee, network.decimals)} ${network.symbol}`, label: `${toDecimalAmount(selected.fee, network.decimals)} ${network.symbol}`,
} }
: { : {
value: 'Custom', value: 'Custom',
localizedValue: l10nCommonMessages.TR_CUSTOM_FEE,
fee: '0', fee: '0',
label: '', label: '',
}; };

@ -3,12 +3,13 @@
import * as SEND from 'actions/constants/send'; import * as SEND from 'actions/constants/send';
import * as ACCOUNT from 'actions/constants/account'; import * as ACCOUNT from 'actions/constants/account';
import type { Action } from 'flowtype'; import type { Action, MessageDescriptor } from 'flowtype';
export type FeeLevel = { export type FeeLevel = {
label: string, label: string,
fee: string, fee: string,
value: string, value: string,
localizedValue?: MessageDescriptor,
}; };
export type State = { export type State = {
@ -33,9 +34,9 @@ export type State = {
destinationTag: string, destinationTag: string,
total: string, total: string,
errors: { [k: string]: string }, errors: { [k: string]: MessageDescriptor },
warnings: { [k: string]: string }, warnings: { [k: string]: MessageDescriptor },
infos: { [k: string]: string }, infos: { [k: string]: MessageDescriptor },
sending: boolean, sending: boolean,
}; };

@ -52,7 +52,7 @@ const TooltipContainer = styled.div`
margin-left: 6px; margin-left: 6px;
`; `;
const getFeeInputState = (feeErrors: string, feeWarnings: string): ?string => { const getFeeInputState = (feeErrors: boolean, feeWarnings: boolean): ?string => {
let state = null; let state = null;
if (feeWarnings && !feeErrors) { if (feeWarnings && !feeErrors) {
state = 'warning'; state = 'warning';
@ -63,7 +63,7 @@ const getFeeInputState = (feeErrors: string, feeWarnings: string): ?string => {
return state; return state;
}; };
const getDestinationTagInputState = (errors: string, warnings: string): ?string => { const getDestinationTagInputState = (errors: boolean, warnings: boolean): ?string => {
let state = null; let state = null;
if (warnings && !errors) { if (warnings && !errors) {
state = 'warning'; state = 'warning';
@ -90,7 +90,7 @@ const AdvancedForm = (props: Props) => {
<AdvancedSettingsWrapper> <AdvancedSettingsWrapper>
<InputRow> <InputRow>
<StyledInput <StyledInput
state={getFeeInputState(errors.fee, warnings.fee)} state={getFeeInputState(!!errors.fee, !!warnings.fee)}
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
@ -125,7 +125,13 @@ const AdvancedForm = (props: Props) => {
</Left> </Left>
</InputLabelWrapper> </InputLabelWrapper>
} }
bottomText={errors.fee || warnings.fee || infos.fee} bottomText={
<>
{(errors.fee && <FormattedMessage {...errors.fee} />) ||
(warnings.fee && <FormattedMessage {...warnings.fee} />) ||
(infos.fee && <FormattedMessage {...infos.fee} />)}
</>
}
value={fee} value={fee}
onChange={event => onFeeChange(event.target.value)} onChange={event => onFeeChange(event.target.value)}
/> />
@ -134,8 +140,8 @@ const AdvancedForm = (props: Props) => {
<InputRow> <InputRow>
<StyledInput <StyledInput
state={getDestinationTagInputState( state={getDestinationTagInputState(
errors.destinationTag, !!errors.destinationTag,
warnings.destinationTag !!warnings.destinationTag
)} )}
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
@ -172,7 +178,17 @@ const AdvancedForm = (props: Props) => {
</InputLabelWrapper> </InputLabelWrapper>
} }
bottomText={ bottomText={
errors.destinationTag || warnings.destinationTag || infos.destinationTag <>
{(errors.destinationTag && (
<FormattedMessage {...errors.destinationTag} />
)) ||
(warnings.destinationTag && (
<FormattedMessage {...warnings.destinationTag} />
)) ||
(infos.destinationTag && (
<FormattedMessage {...infos.destinationTag} />
))}
</>
} }
value={destinationTag} value={destinationTag}
onChange={event => onDestinationTagChange(event.target.value)} onChange={event => onDestinationTagChange(event.target.value)}

@ -202,8 +202,8 @@ const StyledIcon = styled(Icon)`
// render helpers // render helpers
const getAddressInputState = ( const getAddressInputState = (
address: string, address: string,
addressErrors: string, addressErrors: boolean,
addressWarnings: string addressWarnings: boolean
): ?string => { ): ?string => {
let state = null; let state = null;
if (address && !addressErrors) { if (address && !addressErrors) {
@ -218,7 +218,7 @@ const getAddressInputState = (
return state; return state;
}; };
const getAmountInputState = (amountErrors: string, amountWarnings: string): ?string => { const getAmountInputState = (amountErrors: boolean, amountWarnings: boolean): ?string => {
let state = null; let state = null;
if (amountWarnings && !amountErrors) { if (amountWarnings && !amountErrors) {
state = 'warning'; state = 'warning';
@ -317,13 +317,19 @@ const AccountSend = (props: Props) => {
</Title> </Title>
<InputRow> <InputRow>
<Input <Input
state={getAddressInputState(address, errors.address, warnings.address)} state={getAddressInputState(address, !!errors.address, !!warnings.address)}
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
spellCheck="false" spellCheck="false"
topLabel={props.intl.formatMessage(l10nCommonMessages.TR_ADDRESS)} topLabel={props.intl.formatMessage(l10nCommonMessages.TR_ADDRESS)}
bottomText={errors.address || warnings.address || infos.address} bottomText={
<>
{(errors.address && <FormattedMessage {...errors.address} />) ||
(warnings.address && <FormattedMessage {...warnings.address} />) ||
(infos.address && <FormattedMessage {...infos.address} />)}
</>
}
value={address} value={address}
onChange={event => onAddressChange(event.target.value)} onChange={event => onAddressChange(event.target.value)}
sideAddons={[ sideAddons={[
@ -335,7 +341,7 @@ const AccountSend = (props: Props) => {
</InputRow> </InputRow>
<AmountRow> <AmountRow>
<Input <Input
state={getAmountInputState(errors.amount, warnings.amount)} state={getAmountInputState(!!errors.amount, !!warnings.amount)}
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"
@ -357,7 +363,13 @@ const AccountSend = (props: Props) => {
} }
value={amount} value={amount}
onChange={event => onAmountChange(event.target.value)} onChange={event => onAmountChange(event.target.value)}
bottomText={errors.amount || warnings.amount || infos.amount} bottomText={
<>
{(errors.amount && <FormattedMessage {...errors.amount} />) ||
(warnings.amount && <FormattedMessage {...warnings.amount} />) ||
(infos.amount && <FormattedMessage {...infos.amount} />)}
</>
}
sideAddons={[ sideAddons={[
<SetMaxAmountButton key="icon" onClick={() => onSetMax()} isWhite={!setMax}> <SetMaxAmountButton key="icon" onClick={() => onSetMax()} isWhite={!setMax}>
{!setMax && ( {!setMax && (
@ -385,7 +397,7 @@ const AccountSend = (props: Props) => {
<LocalAmountWrapper> <LocalAmountWrapper>
<EqualsSign>=</EqualsSign> <EqualsSign>=</EqualsSign>
<LocalAmountInput <LocalAmountInput
state={getAmountInputState(errors.amount, warnings.amount)} state={getAmountInputState(!!errors.amount, !!warnings.amount)}
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
autoCapitalize="off" autoCapitalize="off"

@ -91,6 +91,44 @@ const definedMessages: Messages = defineMessages({
id: 'TR_DATA_IS_NOT_VALID_HEX', id: 'TR_DATA_IS_NOT_VALID_HEX',
defaultMessage: 'Data is not valid hexadecimal', defaultMessage: 'Data is not valid hexadecimal',
}, },
TR_CANNOT_SEND_TO_MYSELF: {
id: 'TR_CANNOT_SEND_TO_MYSELF',
defaultMessage: 'Cannot send to myself',
},
TR_NOT_ENOUGH_FUNDS_RESERVED_AMOUNT: {
id: 'TR_NOT_ENOUGH_FUNDS_RESERVED_AMOUNT',
defaultMessage:
'Not enough funds. Reserved amount for this account is {reservedAmount} {networkSymbol}',
},
TR_AMOUNT_IS_TOO_LOW_MINIMUM_AMOUNT_FOR_CREATING: {
id: 'TR_AMOUNT_IS_TOO_LOW_MINIMUM_AMOUNT_FOR_CREATING',
defaultMessage:
'Amount is too low. Minimum amount for creating a new account is {minimalAmount} {networkSymbol}',
},
TR_FEE_IS_NOT_SET: {
id: 'TR_FEE_IS_NOT_SET',
defaultMessage: 'Fee is not set',
},
TR_FEE_MUST_ME_AN_ABSOLUT_NUMBER: {
id: 'TR_FEE_MUST_ME_AN_ABSOLUT_NUMBER',
defaultMessage: 'Fee must be an absolute number',
},
TR_FEE_IS_BELOW_RECOMMENDED: {
id: 'TR_FEE_IS_BELOW_RECOMMENDED',
defaultMessage: 'Fee is below recommended',
},
TR_FEE_IS_ABOVE_RECOMMENDED: {
id: 'TR_FEE_IS_ABOVE_RECOMMENDED',
defaultMessage: 'Fee is above recommended',
},
TR_DESTINATION_TAG_MUST_BE_AN_ABSOLUTE: {
id: 'TR_DESTINATION_TAG_MUST_BE_AN_ABSOLUTE',
defaultMessage: 'Destination tag must be an absolute number',
},
TR_DESTINATION_TAG_IS_NOT_VALID: {
id: 'TR_DESTINATION_TAG_IS_NOT_VALID',
defaultMessage: 'Destination tag is not valid',
},
}); });
export default definedMessages; export default definedMessages;

@ -23,8 +23,9 @@ const definedMessages: Messages = defineMessages({
}, },
TR_LOOKS_LIKE_IT_IS_DEVICE_LABEL: { TR_LOOKS_LIKE_IT_IS_DEVICE_LABEL: {
id: 'TR_LOOKS_LIKE_IT_IS_DEVICE_LABEL', id: 'TR_LOOKS_LIKE_IT_IS_DEVICE_LABEL',
defaultMessage: 'Looks like it is {deviceLabel} Account #{number} of {network}', defaultMessage:
description: 'Example: Looks like it is My Trezor Account #1 of ETH', 'Looks like it is {deviceLabel} Account #{number} address of {network} network',
description: 'Example: Looks like it is My Trezor Account #1 address of ETH network',
}, },
TR_IMPORTED_ACCOUNT_HASH: { TR_IMPORTED_ACCOUNT_HASH: {
id: 'TR_IMPORTED_ACCOUNT_HASH', id: 'TR_IMPORTED_ACCOUNT_HASH',

Loading…
Cancel
Save