Merge pull request #3853 from MetaMask/i3580-InternationalizeCurrency

Internationalize currency
feature/default_network_editable
Dan Finlay 7 years ago committed by GitHub
commit 2ce33a3eee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CHANGELOG.md
  2. 134
      development/states/currency-localization.json
  3. 20
      package-lock.json
  4. 1
      package.json
  5. 28
      test/integration/lib/currency-localization.js
  6. 8
      test/integration/lib/send-new-ui.js
  7. 27
      test/unit/balance-formatter-test.js
  8. 18
      ui/app/components/balance-component.js
  9. 19
      ui/app/components/pending-tx/confirm-send-ether.js
  10. 21
      ui/app/components/pending-tx/confirm-send-token.js
  11. 35
      ui/app/components/send/currency-display.js
  12. 2221
      yarn.lock

@ -2,7 +2,8 @@
## Current Master ## Current Master
- Improved performance of 3D fox logo - Correctly format currency conversion for locally selected preferred currency.
- Improved performance of 3D fox logo.
- Fetch token prices based on contract address, not symbol - Fetch token prices based on contract address, not symbol
- Fix bug that prevents setting language locale in settings. - Fix bug that prevents setting language locale in settings.

@ -0,0 +1,134 @@
{
"metamask": {
"isInitialized": true,
"isUnlocked": true,
"featureFlags": {"betaUI": true},
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"name": "Send Account 1"
},
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"name": "Send Account 2"
},
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d": {
"address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d",
"name": "Send Account 3"
},
"0xd85a4b6a394794842887b8284293d69163007bbb": {
"address": "0xd85a4b6a394794842887b8284293d69163007bbb",
"name": "Send Account 4"
}
},
"unapprovedTxs": {},
"currentCurrency": "USD",
"conversionRate": 19855,
"conversionDate": 1489013762,
"noActiveNotices": true,
"frequentRpcList": [],
"network": "3",
"accounts": {
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
"code": "0x",
"balance": "0x47c9d71831c76efe",
"nonce": "0x1b",
"address": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
},
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb": {
"code": "0x",
"balance": "0x37452b1315889f80",
"nonce": "0xa",
"address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb"
},
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d": {
"code": "0x",
"balance": "0x30c9d71831c76efe",
"nonce": "0x1c",
"address": "0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
},
"0xd85a4b6a394794842887b8284293d69163007bbb": {
"code": "0x",
"balance": "0x0",
"nonce": "0x0",
"address": "0xd85a4b6a394794842887b8284293d69163007bbb"
}
},
"addressBook": [
{
"address": "0x06195827297c7a80a443b6894d3bdb8824b43896",
"name": "Address Book Account 1"
}
],
"tokens": [],
"transactions": {},
"selectedAddressTxList": [],
"unapprovedMsgs": {},
"unapprovedMsgCount": 0,
"unapprovedPersonalMsgs": {},
"unapprovedPersonalMsgCount": 0,
"keyringTypes": [
"Simple Key Pair",
"HD Key Tree"
],
"keyrings": [
{
"type": "HD Key Tree",
"accounts": [
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{
"type": "Simple Key Pair",
"accounts": [
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825"
]
}
],
"selectedAddress": "0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"currentCurrency": "PHP",
"provider": {
"type": "testnet"
},
"shapeShiftTxList": [],
"lostAccounts": [],
"send": {
"gasLimit": null,
"gasPrice": null,
"gasTotal": "0xb451dc41b578",
"tokenBalance": null,
"from": "",
"to": "",
"amount": "0x0",
"memo": "",
"errors": {},
"maxModeOn": false,
"editingTransactionId": null
},
"currentLocale": "en"
},
"appState": {
"menuOpen": false,
"currentView": {
"name": "accountDetail",
"detailView": null,
"context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
},
"accountDetail": {
"subview": "transactions"
},
"modal": {
"modalState": {},
"previousModalState": {}
},
"transForward": true,
"isLoading": false,
"warning": null,
"scrollToBottom": false,
"forgottenPassword": null
},
"identities": {}
}

20
package-lock.json generated

@ -344,6 +344,11 @@
"negotiator": "0.6.1" "negotiator": "0.6.1"
} }
}, },
"accounting": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/accounting/-/accounting-0.4.1.tgz",
"integrity": "sha1-h91BA+/39EYPHhhvXGd+1s9WaIM="
},
"acorn": { "acorn": {
"version": "4.0.13", "version": "4.0.13",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
@ -4082,6 +4087,16 @@
"cssom": "0.3.2" "cssom": "0.3.2"
} }
}, },
"currency-formatter": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/currency-formatter/-/currency-formatter-1.4.2.tgz",
"integrity": "sha512-rQ5HB3DenCZwfVPdpVTuVcAORodVO0VoqIbjhdUSuy0sE2b9jBdCaVKbA355NUc2KhPbu5ojHs3WypuEwPLfNg==",
"requires": {
"accounting": "0.4.1",
"locale-currency": "0.0.1",
"object-assign": "4.1.1"
}
},
"currently-unhandled": { "currently-unhandled": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@ -13481,6 +13496,11 @@
"object-assign": "4.1.1" "object-assign": "4.1.1"
} }
}, },
"locale-currency": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/locale-currency/-/locale-currency-0.0.1.tgz",
"integrity": "sha1-yeFaIv9XW0tLuUekv5KsI2vR/ps="
},
"locate-path": { "locate-path": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",

@ -76,6 +76,7 @@
"classnames": "^2.2.5", "classnames": "^2.2.5",
"clone": "^2.1.1", "clone": "^2.1.1",
"copy-to-clipboard": "^3.0.8", "copy-to-clipboard": "^3.0.8",
"currency-formatter": "^1.4.2",
"debounce": "^1.0.0", "debounce": "^1.0.0",
"debounce-stream": "^2.0.0", "debounce-stream": "^2.0.0",
"deep-extend": "^0.5.0", "deep-extend": "^0.5.0",

@ -0,0 +1,28 @@
const reactTriggerChange = require('../../lib/react-trigger-change')
const {
timeout,
queryAsync,
findAsync,
} = require('../../lib/util')
QUnit.module('currency localization')
QUnit.test('renders localized currency', (assert) => {
const done = assert.async()
runCurrencyLocalizationTest(assert).then(done).catch((err) => {
assert.notOk(err, `Error was thrown: ${err.stack}`)
done()
})
})
async function runCurrencyLocalizationTest(assert, done) {
console.log('*** start runCurrencyLocalizationTest')
const selectState = await queryAsync($, 'select')
selectState.val('currency localization')
reactTriggerChange(selectState[0])
await timeout(1000)
const txView = await queryAsync($, '.tx-view')
const heroBalance = await findAsync($(txView), '.hero-balance')
const fiatAmount = await findAsync($(heroBalance), '.fiat-amount')
assert.equal(fiatAmount[0].textContent, '₱102,707.97')
}

@ -91,7 +91,7 @@ async function runSendFlowTest(assert, done) {
) )
assert.equal( assert.equal(
sendGasField.find('.currency-display__converted-value')[0].textContent, sendGasField.find('.currency-display__converted-value')[0].textContent,
'0.24 USD', '$0.24 USD',
'send gas field should show estimated gas total converted to USD' 'send gas field should show estimated gas total converted to USD'
) )
@ -118,7 +118,7 @@ async function runSendFlowTest(assert, done) {
) )
assert.equal( assert.equal(
(await findAsync(sendGasField, '.currency-display__converted-value'))[0].textContent, (await findAsync(sendGasField, '.currency-display__converted-value'))[0].textContent,
'3.60 USD', '$3.60 USD',
'send gas field should show customized gas total converted to USD' 'send gas field should show customized gas total converted to USD'
) )
@ -138,9 +138,9 @@ async function runSendFlowTest(assert, done) {
const confirmScreenRows = await queryAsync($, '.confirm-screen-rows') const confirmScreenRows = await queryAsync($, '.confirm-screen-rows')
const confirmScreenGas = confirmScreenRows.find('.currency-display__converted-value')[0] const confirmScreenGas = confirmScreenRows.find('.currency-display__converted-value')[0]
assert.equal(confirmScreenGas.textContent, '3.60 USD', 'confirm screen should show correct gas') assert.equal(confirmScreenGas.textContent, '$3.60 USD', 'confirm screen should show correct gas')
const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[2] const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[2]
assert.equal(confirmScreenTotal.textContent, '2405.36 USD', 'confirm screen should show correct total') assert.equal(confirmScreenTotal.textContent, '$2,405.36 USD', 'confirm screen should show correct total')
const confirmScreenBackButton = await queryAsync($, '.page-container__back-button') const confirmScreenBackButton = await queryAsync($, '.page-container__back-button')
confirmScreenBackButton[0].click() confirmScreenBackButton[0].click()

@ -0,0 +1,27 @@
const assert = require('assert')
const currencyFormatter = require('currency-formatter')
const infuraConversion = require('../../ui/app/infura-conversion.json')
describe('currencyFormatting', function () {
it('be able to format any infura currency', function (done) {
const number = 10000
infuraConversion.objects.forEach((conversion) => {
const code = conversion.quote.code.toUpperCase()
const result = currencyFormatter.format(number, { code })
switch (code) {
case 'USD':
assert.equal(result, '$10,000.00')
break
case 'JPY':
assert.equal(result, '¥10,000')
break
default:
assert.ok(result, `Currency ${code} formatted as ${result}`)
}
})
done()
})
})

@ -4,6 +4,8 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const TokenBalance = require('./token-balance') const TokenBalance = require('./token-balance')
const Identicon = require('./identicon') const Identicon = require('./identicon')
const currencyFormatter = require('currency-formatter')
const currencies = require('currency-formatter/currencies')
const { formatBalance, generateBalanceObject } = require('../util') const { formatBalance, generateBalanceObject } = require('../util')
@ -97,9 +99,17 @@ BalanceComponent.prototype.renderFiatAmount = function (fiatDisplayNumber, fiatS
const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0 const shouldNotRenderFiat = fiatDisplayNumber === 'N/A' || Number(fiatDisplayNumber) === 0
if (shouldNotRenderFiat) return null if (shouldNotRenderFiat) return null
const upperCaseFiatSuffix = fiatSuffix.toUpperCase()
const display = currencies.find(currency => currency.code === upperCaseFiatSuffix)
? currencyFormatter.format(Number(fiatDisplayNumber), {
code: upperCaseFiatSuffix,
})
: `${fiatPrefix}${fiatDisplayNumber} ${upperCaseFiatSuffix}`
return h('div.fiat-amount', { return h('div.fiat-amount', {
style: {}, style: {},
}, `${fiatPrefix}${fiatDisplayNumber} ${fiatSuffix}`) }, display)
} }
BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) { BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) {
@ -117,5 +127,9 @@ BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, co
const splitBalance = formattedBalance.split(' ') const splitBalance = formattedBalance.split(' ')
return (Number(splitBalance[0]) * conversionRate).toFixed(2) const convertedNumber = (Number(splitBalance[0]) * conversionRate)
const wholePart = Math.floor(convertedNumber)
const decimalPart = convertedNumber - wholePart
return wholePart + Number(decimalPart.toPrecision(2))
} }

@ -23,6 +23,8 @@ const {
const GasFeeDisplay = require('../send/gas-fee-display-v2') const GasFeeDisplay = require('../send/gas-fee-display-v2')
const SenderToRecipient = require('../sender-to-recipient') const SenderToRecipient = require('../sender-to-recipient')
const NetworkDisplay = require('../network-display') const NetworkDisplay = require('../network-display')
const currencyFormatter = require('currency-formatter')
const currencies = require('currency-formatter/currencies')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes') const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
@ -275,6 +277,16 @@ ConfirmSendEther.prototype.getData = function () {
} }
} }
ConfirmSendEther.prototype.convertToRenderableCurrency = function (value, currencyCode) {
const upperCaseCurrencyCode = currencyCode.toUpperCase()
return currencies.find(currency => currency.code === upperCaseCurrencyCode)
? currencyFormatter.format(Number(value), {
code: upperCaseCurrencyCode,
})
: value
}
ConfirmSendEther.prototype.editTransaction = function (txMeta) { ConfirmSendEther.prototype.editTransaction = function (txMeta) {
const { editTransaction, history } = this.props const { editTransaction, history } = this.props
editTransaction(txMeta) editTransaction(txMeta)
@ -319,6 +331,9 @@ ConfirmSendEther.prototype.render = function () {
? 'Increase your gas fee to attempt to overwrite and speed up your transaction' ? 'Increase your gas fee to attempt to overwrite and speed up your transaction'
: 'Please review your transaction.' : 'Please review your transaction.'
const convertedAmountInFiat = this.convertToRenderableCurrency(amountInFIAT, currentCurrency)
const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency)
// This is from the latest master // This is from the latest master
// It handles some of the errors that we are not currently handling // It handles some of the errors that we are not currently handling
// Leaving as comments fo reference // Leaving as comments fo reference
@ -365,7 +380,7 @@ ConfirmSendEther.prototype.render = function () {
// `You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`, // `You're sending to Recipient ...${toAddress.slice(toAddress.length - 4)}`,
// ]), // ]),
h('h3.flex-center.confirm-screen-send-amount', [`${amountInFIAT}`]), h('h3.flex-center.confirm-screen-send-amount', [`${convertedAmountInFiat}`]),
h('h3.flex-center.confirm-screen-send-amount-currency', [ currentCurrency.toUpperCase() ]), h('h3.flex-center.confirm-screen-send-amount-currency', [ currentCurrency.toUpperCase() ]),
h('div.flex-center.confirm-memo-wrapper', [ h('div.flex-center.confirm-memo-wrapper', [
h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]), h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]),
@ -412,7 +427,7 @@ ConfirmSendEther.prototype.render = function () {
]), ]),
h('div.confirm-screen-section-column', [ h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', `${totalInFIAT} ${currentCurrency.toUpperCase()}`), h('div.confirm-screen-row-info', `${convertedTotalInFiat} ${currentCurrency.toUpperCase()}`),
h('div.confirm-screen-row-detail', `${totalInETH} ETH`), h('div.confirm-screen-row-detail', `${totalInETH} ETH`),
]), ]),

@ -27,6 +27,8 @@ const {
calcTokenAmount, calcTokenAmount,
} = require('../../token-util') } = require('../../token-util')
const classnames = require('classnames') const classnames = require('classnames')
const currencyFormatter = require('currency-formatter')
const currencies = require('currency-formatter/currencies')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
@ -318,10 +320,12 @@ ConfirmSendToken.prototype.renderHeroAmount = function () {
const txParams = txMeta.txParams || {} const txParams = txMeta.txParams || {}
const { memo = '' } = txParams const { memo = '' } = txParams
const convertedAmountInFiat = this.convertToRenderableCurrency(fiatAmount, currentCurrency)
return fiatAmount return fiatAmount
? ( ? (
h('div.confirm-send-token__hero-amount-wrapper', [ h('div.confirm-send-token__hero-amount-wrapper', [
h('h3.flex-center.confirm-screen-send-amount', `${fiatAmount}`), h('h3.flex-center.confirm-screen-send-amount', `${convertedAmountInFiat}`),
h('h3.flex-center.confirm-screen-send-amount-currency', currentCurrency), h('h3.flex-center.confirm-screen-send-amount-currency', currentCurrency),
h('div.flex-center.confirm-memo-wrapper', [ h('div.flex-center.confirm-memo-wrapper', [
h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]), h('h3.confirm-screen-send-memo', [ memo ? `"${memo}"` : '' ]),
@ -369,6 +373,9 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
const { fiat: fiatAmount, token: tokenAmount } = this.getAmount() const { fiat: fiatAmount, token: tokenAmount } = this.getAmount()
const { fiat: fiatGas, token: tokenGas } = this.getGasFee() const { fiat: fiatGas, token: tokenGas } = this.getGasFee()
const totalInFIAT = fiatAmount && fiatGas && addCurrencies(fiatAmount, fiatGas)
const convertedTotalInFiat = this.convertToRenderableCurrency(totalInFIAT, currentCurrency)
return fiatAmount && fiatGas return fiatAmount && fiatGas
? ( ? (
h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [ h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [
@ -378,7 +385,7 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
]), ]),
h('div.confirm-screen-section-column', [ h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', `${addCurrencies(fiatAmount, fiatGas)} ${currentCurrency}`), h('div.confirm-screen-row-info', `${convertedTotalInFiat} ${currentCurrency}`),
h('div.confirm-screen-row-detail', `${addCurrencies(tokenAmount, tokenGas || '0')} ${symbol}`), h('div.confirm-screen-row-detail', `${addCurrencies(tokenAmount, tokenGas || '0')} ${symbol}`),
]), ]),
]) ])
@ -413,6 +420,16 @@ ConfirmSendToken.prototype.renderErrorMessage = function (message) {
: null : null
} }
ConfirmSendToken.prototype.convertToRenderableCurrency = function (value, currencyCode) {
const upperCaseCurrencyCode = currencyCode.toUpperCase()
return currencies.find(currency => currency.code === upperCaseCurrencyCode)
? currencyFormatter.format(Number(value), {
code: upperCaseCurrencyCode,
})
: value
}
ConfirmSendToken.prototype.render = function () { ConfirmSendToken.prototype.render = function () {
const txMeta = this.gatherTxMeta() const txMeta = this.gatherTxMeta()
const { const {

@ -3,6 +3,8 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const CurrencyInput = require('../currency-input') const CurrencyInput = require('../currency-input')
const { conversionUtil, multiplyCurrencies } = require('../../conversion-util') const { conversionUtil, multiplyCurrencies } = require('../../conversion-util')
const currencyFormatter = require('currency-formatter')
const currencies = require('currency-formatter/currencies')
module.exports = CurrencyDisplay module.exports = CurrencyDisplay
@ -53,12 +55,32 @@ CurrencyDisplay.prototype.getValueToRender = function () {
}) })
} }
CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValue) {
const { primaryCurrency, convertedCurrency, conversionRate } = this.props
let convertedValue = conversionUtil(nonFormattedValue, {
fromNumericBase: 'dec',
fromCurrency: primaryCurrency,
toCurrency: convertedCurrency,
numberOfDecimals: 2,
conversionRate,
})
convertedValue = Number(convertedValue).toFixed(2)
const upperCaseCurrencyCode = convertedCurrency.toUpperCase()
return currencies.find(currency => currency.code === upperCaseCurrencyCode)
? currencyFormatter.format(Number(convertedValue), {
code: upperCaseCurrencyCode,
})
: convertedValue
}
CurrencyDisplay.prototype.render = function () { CurrencyDisplay.prototype.render = function () {
const { const {
className = 'currency-display', className = 'currency-display',
primaryBalanceClassName = 'currency-display__input', primaryBalanceClassName = 'currency-display__input',
convertedBalanceClassName = 'currency-display__converted-value', convertedBalanceClassName = 'currency-display__converted-value',
conversionRate,
primaryCurrency, primaryCurrency,
convertedCurrency, convertedCurrency,
readOnly = false, readOnly = false,
@ -68,14 +90,7 @@ CurrencyDisplay.prototype.render = function () {
const valueToRender = this.getValueToRender() const valueToRender = this.getValueToRender()
let convertedValue = conversionUtil(valueToRender, { const convertedValueToRender = this.getConvertedValueToRender(valueToRender)
fromNumericBase: 'dec',
fromCurrency: primaryCurrency,
toCurrency: convertedCurrency,
numberOfDecimals: 2,
conversionRate,
})
convertedValue = Number(convertedValue).toFixed(2)
return h('div', { return h('div', {
className, className,
@ -108,7 +123,7 @@ CurrencyDisplay.prototype.render = function () {
h('div', { h('div', {
className: convertedBalanceClassName, className: convertedBalanceClassName,
}, `${convertedValue} ${convertedCurrency.toUpperCase()}`), }, `${convertedValueToRender} ${convertedCurrency.toUpperCase()}`),
]) ])

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save