Merge branch 'master' into selenium-e2e

feature/default_network_editable
Thomas 7 years ago
commit 6306c7b190
  1. 58
      .circleci/config.yml
  2. 1
      .gitignore
  3. 10
      CHANGELOG.md
  4. 1
      README.md
  5. 605
      app/_locales/en/messages.json
  6. 609
      app/_locales/fr/messages.json
  7. 7
      app/manifest.json
  8. 35
      app/scripts/background.js
  9. 62
      app/scripts/lib/local-store.js
  10. 6
      app/scripts/lib/tx-gas-utils.js
  11. 31
      development/genStates.js
  12. 22
      development/mock-dev.js
  13. 2
      development/run-version-bump.js
  14. 10
      development/ui-dev.js
  15. 18
      docs/translating-guide.md
  16. 17
      mascara/src/app/first-time/create-password-screen.js
  17. 128
      mascara/src/app/first-time/index.css
  18. 13
      old-ui/app/keychains/hd/restore-vault.js
  19. 10
      package-lock.json
  20. 14
      package.json
  21. 4
      test/base.conf.js
  22. 73
      test/integration/lib/add-token.js
  23. 44
      test/integration/lib/confirm-sig-requests.js
  24. 80
      test/integration/lib/first-time.js
  25. 94
      test/integration/lib/mascara-first-time.js
  26. 129
      test/integration/lib/send-new-ui.js
  27. 53
      test/lib/util.js
  28. 4
      test/unit/development/version–bump-test.js
  29. 9
      ui/app/accounts/import/index.js
  30. 15
      ui/app/accounts/import/json.js
  31. 13
      ui/app/accounts/import/private-key.js
  32. 6
      ui/app/accounts/import/seed.js
  33. 13
      ui/app/accounts/new-account/create-form.js
  34. 7
      ui/app/accounts/new-account/index.js
  35. 10
      ui/app/actions.js
  36. 28
      ui/app/app.js
  37. 15
      ui/app/components/account-dropdowns.js
  38. 15
      ui/app/components/account-export.js
  39. 17
      ui/app/components/account-menu/index.js
  40. 9
      ui/app/components/bn-as-decimal-input.js
  41. 15
      ui/app/components/buy-button-subview.js
  42. 5
      ui/app/components/coinbase-form.js
  43. 3
      ui/app/components/copyButton.js
  44. 3
      ui/app/components/copyable.js
  45. 25
      ui/app/components/customize-gas-modal/index.js
  46. 22
      ui/app/components/dropdowns/components/account-dropdowns.js
  47. 27
      ui/app/components/dropdowns/network-dropdown.js
  48. 3
      ui/app/components/dropdowns/token-menu-dropdown.js
  49. 5
      ui/app/components/ens-input.js
  50. 9
      ui/app/components/hex-as-decimal-input.js
  51. 5
      ui/app/components/modals/account-details-modal.js
  52. 3
      ui/app/components/modals/account-modal-container.js
  53. 15
      ui/app/components/modals/buy-options-modal.js
  54. 63
      ui/app/components/modals/deposit-ether-modal.js
  55. 7
      ui/app/components/modals/edit-account-name-modal.js
  56. 16
      ui/app/components/modals/export-private-key-modal.js
  57. 13
      ui/app/components/modals/hide-token-confirmation-modal.js
  58. 21
      ui/app/components/modals/modal.js
  59. 17
      ui/app/components/modals/new-account-modal.js
  60. 25
      ui/app/components/network.js
  61. 3
      ui/app/components/notice.js
  62. 4
      ui/app/components/pending-msg-details.js
  63. 15
      ui/app/components/pending-msg.js
  64. 4
      ui/app/components/pending-personal-msg-details.js
  65. 29
      ui/app/components/pending-tx/confirm-deploy-contract.js
  66. 21
      ui/app/components/pending-tx/confirm-send-ether.js
  67. 35
      ui/app/components/pending-tx/confirm-send-token.js
  68. 3
      ui/app/components/pending-typed-msg-details.js
  69. 11
      ui/app/components/pending-typed-msg.js
  70. 35
      ui/app/components/send-token/index.js
  71. 4
      ui/app/components/send/gas-fee-display-v2.js
  72. 4
      ui/app/components/send/gas-tooltip.js
  73. 4
      ui/app/components/send/to-autocomplete.js
  74. 21
      ui/app/components/shapeshift-form.js
  75. 15
      ui/app/components/shift-list-item.js
  76. 25
      ui/app/components/signature-request.js
  77. 7
      ui/app/components/token-list.js
  78. 17
      ui/app/components/transaction-list-item.js
  79. 4
      ui/app/components/transaction-list.js
  80. 3
      ui/app/components/tx-list-item.js
  81. 5
      ui/app/components/tx-list.js
  82. 21
      ui/app/components/tx-view.js
  83. 9
      ui/app/components/wallet-view.js
  84. 3
      ui/app/css/itcss/components/hero-balance.scss
  85. 3
      ui/app/css/itcss/components/index.scss
  86. 50
      ui/app/css/itcss/components/modal.scss
  87. 3
      ui/app/css/itcss/components/new-account.scss
  88. 21
      ui/app/css/itcss/components/newui-sections.scss
  89. 59
      ui/app/css/itcss/components/welcome-screen.scss
  90. 37
      ui/app/css/itcss/generic/index.scss
  91. 19
      ui/app/first-time/init-menu.js
  92. 13
      ui/app/keychains/hd/restore-vault.js
  93. 14
      ui/app/main-container.js
  94. 6
      ui/app/reducers/metamask.js
  95. 16
      ui/app/unlock.js
  96. 56
      ui/app/welcome-screen.js
  97. 33
      ui/i18n.js

@ -15,12 +15,20 @@ workflows:
- test-unit:
requires:
- prep-deps-npm
- test-integration-mascara:
- test-integration-mascara-chrome:
requires:
- prep-deps-npm
- prep-scss
- test-integration-mascara-firefox:
requires:
- prep-deps-npm
- prep-deps-firefox
- prep-scss
- test-integration-flat:
- test-integration-flat-chrome:
requires:
- prep-deps-npm
- prep-scss
- test-integration-flat-firefox:
requires:
- prep-deps-npm
- prep-deps-firefox
@ -99,7 +107,9 @@ jobs:
name: test:coverage
command: npm run test:coverage
test-integration-flat:
test-integration-flat-firefox:
environment:
browsers: '["Firefox"]'
docker:
- image: circleci/node:8-browsers
steps:
@ -125,7 +135,28 @@ jobs:
name: test:integration:flat
command: npm run test:flat
test-integration-mascara:
test-integration-flat-chrome:
environment:
browsers: '["Chrome"]'
docker:
- image: circleci/node:8-browsers
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
- run:
name: Get Scss Cache key
# this allows us to checksum against a whole directory
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum
- restore_cache:
key: scss-cache-{{ checksum "scss_checksum" }}
- run:
name: test:integration:flat
command: npm run test:flat
test-integration-mascara-firefox:
environment:
browsers: '["Firefox"]'
docker:
- image: circleci/node:8-browsers
steps:
@ -150,3 +181,22 @@ jobs:
- run:
name: test:integration:mascara
command: npm run test:mascara
test-integration-mascara-chrome:
environment:
browsers: '["Chrome"]'
docker:
- image: circleci/node:8-browsers
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
- run:
name: Get Scss Cache key
# this allows us to checksum against a whole directory
command: find ui/app/css -type f -exec md5sum {} \; | sort -k 2 > scss_checksum
- restore_cache:
key: scss-cache-{{ checksum "scss_checksum" }}
- run:
name: test:integration:mascara
command: npm run test:mascara

1
.gitignore vendored

@ -1,5 +1,6 @@
npm-debug.log
node_modules
yarn.lock
app/bower_components
test/bower_components

@ -1,7 +1,17 @@
# Changelog
## Current Master
- Add ability for internationalization.
- Will now throw an error if the `to` field in txParams is not valid.
- Will strip null values from the `to` field.
- Fix flashing to Log in screen after logging in or restoring from seed phrase.
- Increase tap areas for menu buttons on mobile
- Change all fonts in new-ui onboarding to Roboto, size 400
- Add a welcome screen to new-ui onboarding flow
- Make new-ui create password screen responsive
- Hide network dropdown before account is initialized
- Fix bug that could prevent MetaMask from saving the latest vault.
## 4.2.0 Tue Mar 06 2018

@ -66,6 +66,7 @@ To write tests that will be run in the browser using QUnit, add your test files
- [How to add custom build to Chrome](./docs/add-to-chrome.md)
- [How to add custom build to Firefox](./docs/add-to-firefox.md)
- [How to develop a live-reloading UI](./docs/ui-dev-mode.md)
- [How to add a new translation to MetaMask](./docs/translating-guide.md)
- [Publishing Guide](./docs/publishing.md)
- [How to develop an in-browser mocked UI](./docs/ui-mock-mode.md)
- [How to live reload on local dependency changes](./docs/developing-on-deps.md)

@ -1,10 +1,609 @@
{
"accept": {
"message": "Accept"
},
"account": {
"message": "Account"
},
"accountDetails": {
"message": "Account Details"
},
"accountName": {
"message": "Account Name"
},
"address": {
"message": "Address"
},
"addToken": {
"message": "Add Token"
},
"amount": {
"message": "Amount"
},
"amountPlusGas": {
"message": "Amount + Gas"
},
"appDescription": {
"message": "Ethereum Browser Extension",
"description": "The description of the application"
},
"appName": {
"message": "MetaMask",
"description": "The name of the application"
},
"appDescription": {
"message": "Ethereum Identity Management",
"description": "The description of the application"
"attemptingConnect": {
"message": "Attempting to connect to blockchain."
},
"available": {
"message": "Available"
},
"back": {
"message": "Back"
},
"balance": {
"message": "Balance:"
},
"balanceIsInsufficientGas": {
"message": "Insufficient balance for current gas total"
},
"beta": {
"message": "BETA"
},
"betweenMinAndMax": {
"message": "must be greater than or equal to $1 and less than or equal to $2.",
"description": "helper for inputting hex as decimal input"
},
"borrowDharma": {
"message": "Borrow With Dharma (Beta)"
},
"buy": {
"message": "Buy"
},
"buyCoinbase": {
"message": "Buy on Coinbase"
},
"buyCoinbaseExplainer": {
"message": "Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin."
},
"cancel": {
"message": "Cancel"
},
"clickCopy": {
"message": "Click to Copy"
},
"confirm": {
"message": "Confirm"
},
"confirmContract": {
"message": "Confirm Contract"
},
"confirmPassword": {
"message": "Confirm Password"
},
"confirmTransaction": {
"message": "Confirm Transaction"
},
"continueToCoinbase": {
"message": "Continue to Coinbase"
},
"contractDeployment": {
"message": "Contract Deployment"
},
"conversionProgress": {
"message": "Conversion in progress"
},
"copiedButton": {
"message": "Copied"
},
"copiedClipboard": {
"message": "Copied to Clipboard"
},
"copiedExclamation": {
"message": "Copied!"
},
"copy": {
"message": "Copy"
},
"copyToClipboard": {
"message": "Copy to clipboard"
},
"copyButton": {
"message": " Copy "
},
"copyPrivateKey": {
"message": "This is your private key (click to copy)"
},
"create": {
"message": "Create"
},
"createAccount": {
"message": "Create Account"
},
"createDen": {
"message": "Create"
},
"crypto": {
"message": "Crypto",
"description": "Exchange type (cryptocurrencies)"
},
"customGas": {
"message": "Customize Gas"
},
"customize": {
"message": "Customize"
},
"customRPC": {
"message": "Custom RPC"
},
"defaultNetwork": {
"message": "The default network for Ether transactions is Main Net."
},
"denExplainer": {
"message": "Your DEN is your password-encrypted storage within MetaMask."
},
"deposit": {
"message": "Deposit"
},
"depositBTC": {
"message": "Deposit your BTC to the address below:"
},
"depositCoin": {
"message": "Deposit your $1 to the address below",
"description": "Tells the user what coin they have selected to deposit with shapeshift"
},
"depositEth": {
"message": "Deposit Eth"
},
"depositEther": {
"message": "Deposit Ether"
},
"depositFiat": {
"message": "Deposit with Fiat"
},
"depositFromAccount": {
"message": "Deposit from another account"
},
"depositShapeShift": {
"message": "Deposit with ShapeShift"
},
"depositShapeShiftExplainer": {
"message": "If you own other cryptocurrencies, you can trade and deposit Ether directly into your MetaMask wallet. No Account Needed."
},
"details": {
"message": "Details"
},
"directDeposit": {
"message": "Direct Deposit"
},
"directDepositEther": {
"message": "Directly Deposit Ether"
},
"directDepositEtherExplainer": {
"message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit."
},
"done": {
"message": "Done"
},
"edit": {
"message": "Edit"
},
"editAccountName": {
"message": "Edit Account Name"
},
"encryptNewDen": {
"message": "Encrypt your new DEN"
},
"enterPassword": {
"message": "Enter password"
},
"etherscanView": {
"message": "View account on Etherscan"
},
"exchangeRate": {
"message": "Exchange Rate"
},
"exportPrivateKey": {
"message": "Export Private Key"
},
"exportPrivateKeyWarning": {
"message": "Export private keys at your own risk."
},
"failed": {
"message": "Failed"
},
"fiat": {
"message": "FIAT",
"description": "Exchange type"
},
"fileImportFail": {
"message": "File import not working? Click here!",
"description": "Helps user import their account from a JSON file"
},
"from": {
"message": "From"
},
"fromShapeShift": {
"message": "From ShapeShift"
},
"gas": {
"message": "Gas",
"description": "Short indication of gas cost"
},
"gasFee": {
"message": "Gas Fee"
},
"gasLimit": {
"message": "Gas Limit"
},
"gasLimitCalculation": {
"message": "We calculate the suggested gas limit based on network success rates."
},
"gasLimitRequired": {
"message": "Gas Limit Required"
},
"gasLimitTooLow": {
"message": "Gas limit must be at least 21000"
},
"gasPrice": {
"message": "Gas Price (GWEI)"
},
"gasPriceCalculation": {
"message": "We calculate the suggested gas prices based on network success rates."
},
"gasPriceRequired": {
"message": "Gas Price Required"
},
"getEther": {
"message": "Get Ether"
},
"getEtherFromFaucet": {
"message": "Get Ether from a faucet for the $1",
"description": "Displays network name for Ether faucet"
},
"greaterThanMin": {
"message": "must be greater than or equal to $1.",
"description": "helper for inputting hex as decimal input"
},
"here": {
"message": "here",
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
},
"hide": {
"message": "Hide"
},
"hideToken": {
"message": "Hide Token"
},
"hideTokenPrompt": {
"message": "Hide Token?"
},
"howToDeposit": {
"message": "How would you like to deposit Ether?"
},
"import": {
"message": "Import",
"description": "Button to import an account from a selected file"
},
"importAccount": {
"message": "Import Account"
},
"importAnAccount": {
"message": "Import an account"
},
"importDen": {
"message": "Import Existing DEN"
},
"imported": {
"message": "Imported",
"description": "status showing that an account has been fully loaded into the keyring"
},
"infoHelp": {
"message": "Info & Help"
},
"invalidAddress": {
"message": "Invalid address"
},
"invalidGasParams": {
"message": "Invalid Gas Parameters"
},
"invalidInput": {
"message": "Invalid input."
},
"invalidRequest": {
"message": "Invalid Request"
},
"jsonFile": {
"message": "JSON File",
"description": "format for importing an account"
},
"kovan": {
"message": "Kovan Test Network"
},
"lessThanMax": {
"message": "must be less than or equal to $1.",
"description": "helper for inputting hex as decimal input"
},
"limit": {
"message": "Limit"
},
"loading": {
"message": "Loading..."
},
"loadingTokens": {
"message": "Loading Tokens..."
},
"localhost": {
"message": "Localhost 8545"
},
"logout": {
"message": "Log out"
},
"loose": {
"message": "Loose"
},
"mainnet": {
"message": "Main Ethereum Network"
},
"message": {
"message": "Message"
},
"min": {
"message": "Minimum"
},
"myAccounts": {
"message": "My Accounts"
},
"needEtherInWallet": {
"message": "To interact with decentralized applications using MetaMask, you’ll need Ether in your wallet."
},
"needImportFile": {
"message": "You must select a file to import.",
"description": "User is important an account and needs to add a file to continue"
},
"needImportPassword": {
"message": "You must enter a password for the selected file.",
"description": "Password and file needed to import an account"
},
"networks": {
"message": "Networks"
},
"newAccount": {
"message": "New Account"
},
"newAccountNumberName": {
"message": "Account $1",
"description": "Default name of next account to be created on create account screen"
},
"newContract": {
"message": "New Contract"
},
"newPassword": {
"message": "New Password (min 8 chars)"
},
"newRecipient": {
"message": "New Recipient"
},
"next": {
"message": "Next"
},
"noAddressForName": {
"message": "No address has been set for this name."
},
"noDeposits": {
"message": "No deposits received"
},
"noTransactionHistory": {
"message": "No transaction history."
},
"noTransactions": {
"message": "No Transactions"
},
"notStarted": {
"message": "Not Started"
},
"oldUI": {
"message": "Old UI"
},
"oldUIMessage": {
"message": "You have returned to the old UI. You can switch back to the New UI through the option in the top right dropdown menu."
},
"or": {
"message": "or",
"description": "choice between creating or importing a new account"
},
"passwordMismatch": {
"message": "passwords don't match",
"description": "in password creation process, the two new password fields did not match"
},
"passwordShort": {
"message": "password not long enough",
"description": "in password creation process, the password is not long enough to be secure"
},
"pastePrivateKey": {
"message": "Paste your private key string here:",
"description": "For importing an account from a private key"
},
"pasteSeed": {
"message": "Paste your seed phrase here!"
},
"pleaseReviewTransaction": {
"message": "Please review your transaction."
},
"privateKey": {
"message": "Private Key",
"description": "select this type of file to use to import an account"
},
"privateKeyWarning": {
"message": "Warning: Never disclose this key. Anyone with your private keys can take steal any assets held in your account."
},
"privateNetwork": {
"message": "Private Network"
},
"qrCode": {
"message": "Show QR Code"
},
"readdToken": {
"message": "You can add this token back in the future by going go to “Add token” in your accounts options menu."
},
"readMore": {
"message": "Read more here."
},
"receive": {
"message": "Receive"
},
"recipientAddress": {
"message": "Recipient Address"
},
"refundAddress": {
"message": "Your Refund Address"
},
"rejected": {
"message": "Rejected"
},
"required": {
"message": "Required"
},
"retryWithMoreGas": {
"message": "Retry with a higher gas price here"
},
"revert": {
"message": "Revert"
},
"rinkeby": {
"message": "Rinkeby Test Network"
},
"ropsten": {
"message": "Ropsten Test Network"
},
"sampleAccountName": {
"message": "E.g. My new account",
"description": "Help user understand concept of adding a human-readable name to their account"
},
"save": {
"message": "Save"
},
"saveAsFile": {
"message": "Save as File",
"description": "Account export process"
},
"selectService": {
"message": "Select Service"
},
"send": {
"message": "Send"
},
"sendTokens": {
"message": "Send Tokens"
},
"sendTokensAnywhere": {
"message": "Send Tokens to anyone with an Ethereum account"
},
"settings": {
"message": "Settings"
},
"shapeshiftBuy": {
"message": "Buy with Shapeshift"
},
"showPrivateKeys": {
"message": "Show Private Keys"
},
"showQRCode": {
"message": "Show QR Code"
},
"sign": {
"message": "Sign"
},
"signMessage": {
"message": "Sign Message"
},
"signNotice": {
"message": "Signing this message can have \ndangerous side effects. Only sign messages from \nsites you fully trust with your entire account.\n This dangerous method will be removed in a future version. "
},
"sigRequest": {
"message": "Signature Request"
},
"sigRequested": {
"message": "Signature Requested"
},
"status": {
"message": "Status"
},
"submit": {
"message": "Submit"
},
"takesTooLong": {
"message": "Taking too long?"
},
"testFaucet": {
"message": "Test Faucet"
},
"to": {
"message": "To"
},
"toETHviaShapeShift": {
"message": "$1 to ETH via ShapeShift",
"description": "system will fill in deposit type in start of message"
},
"tokenBalance": {
"message": "Your Token Balance is:"
},
"total": {
"message": "Total"
},
"transactionMemo": {
"message": "Transaction memo (optional)"
},
"transactionNumber": {
"message": "Transaction Number"
},
"transfers": {
"message": "Transfers"
},
"troubleTokenBalances": {
"message": "We had trouble loading your token balances. You can view them ",
"description": "Followed by a link (here) to view token balances"
},
"typePassword": {
"message": "Type Your Password"
},
"uiWelcome": {
"message": "Welcome to the New UI (Beta)"
},
"uiWelcomeMessage": {
"message": "You are now using the new Metamask UI. Take a look around, try out new features like sending tokens, and let us know if you have any issues."
},
"unavailable": {
"message": "Unavailable"
},
"unknown": {
"message": "Unknown"
},
"unknownNetwork": {
"message": "Unknown Private Network"
},
"unknownNetworkId": {
"message": "Unknown network ID"
},
"usaOnly": {
"message": "USA only",
"description": "Using this exchange is limited to people inside the USA"
},
"usedByClients": {
"message": "Used by a variety of different clients"
},
"viewAccount": {
"message": "View Account"
},
"warning": {
"message": "Warning"
},
"whatsThis": {
"message": "What's this?"
},
"yourSigRequested": {
"message": "Your signature is being requested"
},
"youSign": {
"message": "You are signing"
}
}

@ -0,0 +1,609 @@
{
"accept": {
"message": "Accepter"
},
"account": {
"message": "Compte"
},
"accountDetails": {
"message": "Détails du compte"
},
"accountName": {
"message": "Nom du compte"
},
"address": {
"message": "Adresse"
},
"addToken": {
"message": "Ajouter un jeton"
},
"amount": {
"message": "Montant"
},
"amountPlusGas": {
"message": "Montant + Gaz"
},
"appDescription": {
"message": "Extension Ethereum pour navigateur",
"description": "La description de l'application"
},
"appName": {
"message": "MetaMask",
"description": "Le nom de l'application"
},
"attemptingConnect": {
"message": "Tentative de connexion à blockchain."
},
"available": {
"message": "Disponible"
},
"back": {
"message": "Retour"
},
"balance": {
"message": "Balance:"
},
"balanceIsInsufficientGas": {
"message": "Solde insuffisant pour le total actuel de gaz"
},
"beta": {
"message": "BETA"
},
"betweenMinAndMax": {
"message": "doit être supérieur ou égal à $1 et inférieur ou égal à $2",
"description": "helper pour la saisie hexadécimale en entrée décimale"
},
"borrowDharma": {
"message": "Emprunter avec Dharma (Bêta)"
},
"buy": {
"message": "Acheter"
},
"buyCoinbase": {
"message": "Acheter sur Coinbase"
},
"buyCoinbaseExplainer": {
"message": "Coinbase est le moyen le plus populaire au monde d'acheter et de vendre du bitcoin, de l'ethereum et du litecoin."
},
"cancel": {
"message": "Annuler"
},
"clickCopy": {
"message": "Cliquer pour copier"
},
"confirm": {
"message": "Confirmer"
},
"confirmContract": {
"message": "Confirmer le contrat"
},
"confirmPassword": {
"message": "Confirmer le mot de passe"
},
"confirmTransaction": {
"message": "Confirmer la transaction"
},
"continueToCoinbase": {
"message": "Continuer vers Coinbase"
},
"contractDeployment": {
"message": "Déploiement de contrat"
},
"conversionProgress": {
"message": "Conversion en cours"
},
"copiedButton": {
"message": "Copié"
},
"copiedClipboard": {
"message": "Copié dans le Presse-papiers"
},
"copiedExclamation": {
"message": "Copié!"
},
"copy": {
"message": "Copier"
},
"copyToClipboard": {
"message": "Copier dans le presse-papier"
},
"copyButton": {
"message": " Copier "
},
"copyPrivateKey": {
"message": "Ceci est votre clé privée (cliquez pour copier)"
},
"create": {
"message": "Créer"
},
"createAccount": {
"message": "Créer un compte"
},
"createDen": {
"message": "Créer"
},
"crypto": {
"message": "Crypto",
"description": "Type d'échange (cryptocurrencies)"
},
"customGas": {
"message": "Personnaliser le Gaz"
},
"customize": {
"message": "Personnaliser"
},
"customRPC": {
"message": "RPC personnalisé"
},
"defaultNetwork": {
"message": "Le réseau par défaut pour les transactions Ether est Main Net."
},
"denExplainer": {
"message": "Votre DEN est votre stockage crypté par mot de passe dans MetaMask."
},
"deposit": {
"message": "Dépôt"
},
"depositBTC": {
"message": "Déposez vos BTC à l'adresse ci-dessous:"
},
"depositCoin": {
"message": "Déposez votre $1 à l'adresse ci-dessous",
"description": "Indique à l'utilisateur quelle monnaie ils a choisi de déposer avec shapeshift"
},
"depositEth": {
"message": "Dépôt Eth"
},
"depositEther": {
"message": "Dépôt Ether"
},
"depositFiat": {
"message": "Dépôt de monnaie-fiat"
},
"depositFromAccount": {
"message": "Dépôt d'un autre compte"
},
"depositShapeShift": {
"message": "Déposez avec ShapeShift"
},
"depositShapeShiftExplainer": {
"message": "Si vous possédez d'autres crypto-monnaies, vous pouvez échanger et déposer de l'Ether directement dans votre portefeuille MetaMask. Aucun compte n'est requis."
},
"details": {
"message": "Détails"
},
"directDeposit": {
"message": "Dépôt direct"
},
"directDepositEther": {
"message": "Dépôt direct d'Ether"
},
"directDepositEtherExplainer": {
"message": "Si vous avez déjà de l'Ether, le moyen le plus rapide d'obtenir des Ether dans votre nouveau portefeuille est par dépôt direct."
},
"done": {
"message": "Fait"
},
"edit": {
"message": "Modifier"
},
"editAccountName": {
"message": "Modifier le nom du compte"
},
"encryptNewDen": {
"message": "Chiffrer votre nouveau DEN"
},
"enterPassword": {
"message": "Entrer le mot de passe"
},
"etherscanView": {
"message": "Afficher le compte sur Etherscan"
},
"exchangeRate": {
"message": "Taux de change"
},
"exportPrivateKey": {
"message": "Exporter la clé privée"
},
"exportPrivateKeyWarning": {
"message": "Exporter les clés privées à vos risques et périls."
},
"failed": {
"message": "Échec"
},
"fiat": {
"message": "FIAT",
"description": "Type d'échange"
},
"fileImportFail": {
"message": "L'importation de fichier ne fonctionne pas? Cliquez ici!",
"description": "Aide l'utilisateur à importer son compte à partir d'un fichier JSON"
},
"from": {
"message": "de"
},
"fromShapeShift": {
"message": "ShapeShift de"
},
"gas": {
"message": "Gas",
"description": "Indication courte du coût du gaz"
},
"gasFee": {
"message": "Frais de gaz"
},
"gasLimit": {
"message": "Limite de gaz"
},
"gasLimitCalculation": {
"message": "Nous calculons la limite de gaz suggérée en fonction des taux de réussite du réseau."
},
"gasLimitRequired": {
"message": "Limite de gaz requise"
},
"gasLimitTooLow": {
"message": "La limite de gaz doit être d'au moins 21000"
},
"gasPrice": {
"message": "Prix du gaz (GWEI)"
},
"gasPriceCalculation": {
"message": "Nous calculons les prix du gaz proposés en fonction des taux de réussite du réseau."
},
"gasPriceRequired": {
"message": "Prix du gaz requis"
},
"getEther": {
"message": "Obtenir des Ether"
},
"getEtherFromFaucet": {
"message": "Obtenir de l'Ether d'une faucet pour $1",
"description": "Affiche le nom du réseau pour la faucet d'Ether"
},
"greaterThanMin": {
"message": "doit être supérieur ou égal à $1.",
"description": "helper pour la saisie hexadécimale en entrée décimale"
},
"here": {
"message": "ici",
"description": "comme dans -cliquer ici- pour plus d'informations (en rapport avec troubleTokenBalances)"
},
"hide": {
"message": "Cacher"
},
"hideToken": {
"message": "Masquer le jeton"
},
"hideTokenPrompt": {
"message": "Masquer le jeton?"
},
"howToDeposit": {
"message": "Comment voulez-vous déposer de l'Ether?"
},
"import": {
"message": "Importer",
"description": "Bouton pour importer un compte à partir d'un fichier sélectionné"
},
"importAccount": {
"message": "Importer compte"
},
"importAnAccount": {
"message": "Importer un compte"
},
"importDen": {
"message": "Importer DEN existant"
},
"imported": {
"message": "Importé",
"description": "statut indiquant qu'un compte a été entièrement chargé dans le trousseau de clés"
},
"infoHelp": {
"message": "Info & Aide"
},
"invalidAddress": {
"message": "Adresse invalide"
},
"invalidGasParams": {
"message": "Paramètres de gaz invalides"
},
"invalidInput": {
"message": "Entrée non valide."
},
"invalidRequest": {
"message": "Requête invalide"
},
"jsonFile": {
"message": "Fichier JSON",
"description": "format d'importation d'un compte"
},
"kovan": {
"message": "Réseau de test Kovan"
},
"lessThanMax": {
"message": "doit être inférieur ou égal à $1.",
"description": "helper pour la saisie hexadécimale en entrée décimale"
},
"limit": {
"message": "Limite"
},
"loading": {
"message": "Chargement..."
},
"loadingTokens": {
"message": "Chargement des jetons..."
},
"localhost": {
"message": "Localhost 8545"
},
"logout": {
"message": "Déconnexion"
},
"loose": {
"message": "Vacant"
},
"mainnet": {
"message": "Réseau principal Ethereum"
},
"message": {
"message": "Message"
},
"min": {
"message": "Minimum"
},
"myAccounts": {
"message": "Mes comptes"
},
"needEtherInWallet": {
"message": "Pour interagir avec des applications décentralisées à l'aide de MetaMask, vous aurez besoin d'Ether dans votre portefeuille."
},
"needImportFile": {
"message": "Vous devez sélectionner un fichier à importer.",
"description": "L'utilisateur doit ajouter un fichier pour continuer"
},
"needImportPassword": {
"message": "Vous devez entrer un mot de passe pour le fichier sélectionné.",
"description": "Mot de passe et fichier requis pour importer un compte"
},
"networks": {
"message": "Réseaux"
},
"newAccount": {
"message": "Nouveau compte"
},
"newAccountNumberName": {
"message": "Compte $1",
"description": "Nom par défaut du compte suivant à créer sur l'écran de création de compte"
},
"newContract": {
"message": "Nouveau contrat"
},
"newPassword": {
"message": "Nouveau mot de passe (min 8 caractères)"
},
"newRecipient": {
"message": "Nouveau destinataire"
},
"next": {
"message": "Suivant"
},
"noAddressForName": {
"message": "Aucune adresse n'a été définie pour ce nom."
},
"noDeposits": {
"message": "Aucun dépôt reçu"
},
"noTransactionHistory": {
"message": "Aucun historique de transaction."
},
"noTransactions": {
"message": "Aucune transaction"
},
"notStarted": {
"message": "Pas démarré"
},
"oldUI": {
"message": "Ancienne interface utilisateur"
},
"oldUIMessage": {
"message": "Vous êtes revenu à l'ancienne interface utilisateur.Vous pouvez revenir à la nouvelle interface via l'option dans le menu déroulant en haut à droite."
},
"or": {
"message": "ou",
"description": "choix entre la création ou l'importation d'un nouveau compte"
},
"passwordMismatch": {
"message": "les mots de passe ne correspondent pas",
"description": "dans le processus de création de mot de passe, les deux nouveaux champs de mot de passe ne correspondent pas"
},
"passwordShort": {
"message": "mot de passe pas assez long",
"description": "dans le processus de création de mot de passe, le mot de passe n'est pas assez long pour être sécurisé"
},
"pastePrivateKey": {
"message": "Collez votre clé privée ici:",
"description": "Pour l'importation d'un compte à partir d'une clé privée"
},
"pasteSeed": {
"message": "Collez votre seed phrase ici!"
},
"pleaseReviewTransaction": {
"message": "Veuillez vérifier votre transaction."
},
"privateKey": {
"message": "Clé privée",
"description": "sélectionnez ce type de fichier à utiliser pour importer un compte"
},
"privateKeyWarning": {
"message": "Avertissement: Ne divulguez jamais cette clé, quiconque avec vos clés privées peut voler tous les actifs de votre compte."
},
"privateNetwork": {
"message": "Réseau privé"
},
"qrCode": {
"message": "Afficher le QR Code"
},
"readdToken": {
"message": "Vous pouvez ajouter ce jeton dans le futur en allant sur “Ajouter un jeton” dans le menu des options de votre compte."
},
"readMore": {
"message": "En savoir plus ici."
},
"receive": {
"message": "Recevoir"
},
"recipientAddress": {
"message": "Adresse du destinataire"
},
"refundAddress": {
"message": "Votre adresse de remboursement"
},
"rejected": {
"message": "Rejeté"
},
"required": {
"message": "Obligatoire"
},
"retryWithMoreGas": {
"message": "Réessayez avec un prix plus élevé du gaz ici"
},
"revert": {
"message": "Rétablir"
},
"rinkeby": {
"message": "Réseau de test Rinkeby"
},
"ropsten": {
"message": "Réseau de test Ropsten"
},
"sampleAccountName": {
"message": "Par exemple mon nouveau compte",
"description": "Aidez l'utilisateur à comprendre le concept d'ajout d'un nom lisible par un humain à son compte"
},
"save": {
"message": "Enregistrer"
},
"saveAsFile": {
"message": "Enregistrer dans un fichier",
"description": "Processus d'exportation de compte"
},
"selectService": {
"message": "Sélectionner un service"
},
"send": {
"message": "Envoyé"
},
"sendTokens": {
"message": "Envoyer des jetons"
},
"sendTokensAnywhere": {
"message": "Envoyer des jetons à toute personne possédant un compte Ethereum"
},
"settings": {
"message": "Paramètres"
},
"shapeshiftBuy": {
"message": "Acheter avec Shapeshift"
},
"showPrivateKeys": {
"message": "Afficher les clés privées"
},
"showQRCode": {
"message": "Afficher le QR Code"
},
"sign": {
"message": "Signer"
},
"signMessage": {
"message": "Signer le message"
},
"signNotice": {
"message": "La signature de ce message peut avoir des effets secondaires \ndangereux. Signer uniquement les messages de \nsites auxquels vous faites entièrement confiance avec votre compte.\n Cette méthode dangereuse sera supprimée dans une future version."
},
"sigRequest": {
"message": "Demande de signature"
},
"sigRequested": {
"message": "Signature demandée"
},
"status": {
"message": "Statut"
},
"submit": {
"message": "Soumettre"
},
"takesTooLong": {
"message": "Prend trop de temps?"
},
"testFaucet": {
"message": "Test Faucet"
},
"to": {
"message": "Destinataire"
},
"toETHviaShapeShift": {
"message": "$1 à ETH via ShapeShift",
"description": "le système remplira le type de dépôt au début du message"
},
"tokenBalance": {
"message": "Votre solde de jeton est:"
},
"total": {
"message": "Total"
},
"transactionMemo": {
"message": "Mémo de transaction (optionnel)"
},
"transactionNumber": {
"message": "Numéro de transaction"
},
"transfers": {
"message": "Transferts"
},
"troubleTokenBalances": {
"message": "Nous avons eu du mal à charger votre balance de jetons, vous pouvez la consulter ",
"description": "Suivi par un lien (ici) pour voir les soldes des jetons"
},
"typePassword": {
"message": "Entrez votre mot de passe"
},
"uiWelcome": {
"message": "Bienvenue dans la nouvelle interface utilisateur (Beta)"
},
"uiWelcomeMessage": {
"message": "Vous utilisez maintenant la nouvelle interface utilisateur Metamask. Jetez un coup d'oeil, essayez de nouvelles fonctionnalités comme l'envoi de jetons, et faites-nous savoir si vous avez des problèmes."
},
"unavailable": {
"message": "Indisponible"
},
"unknown": {
"message": "Inconnu"
},
"unknownNetwork": {
"message": "Réseau privé inconnu"
},
"unknownNetworkId": {
"message": "ID réseau inconnu"
},
"usaOnly": {
"message": "Etats-Unis seulement",
"description": "Utiliser cet échange est limité aux personnes à l'intérieur des Etats-Unis"
},
"usedByClients": {
"message": "Utilisé par une variété de clients différents"
},
"viewAccount": {
"message": "Afficher le compte"
},
"warning": {
"message": "Avertissement"
},
"whatsThis": {
"message": "Qu'est-ce que c'est?"
},
"yourSigRequested": {
"message": "Votre signature est demandée"
},
"youSign": {
"message": "Vous signez"
}
}

@ -1,10 +1,10 @@
{
"name": "MetaMask",
"short_name": "Metamask",
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
"version": "4.2.0",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "Ethereum Browser Extension",
"description": "__MSG_appDescription__",
"commands": {
"_execute_browser_action": {
"suggested_key": {
@ -56,6 +56,7 @@
],
"permissions": [
"storage",
"unlimitedStorage",
"clipboardWrite",
"http://localhost:8545/",
"https://*.infura.io/"

@ -1,9 +1,11 @@
const urlUtil = require('url')
const endOfStream = require('end-of-stream')
const pump = require('pump')
const debounce = require('debounce-stream')
const log = require('loglevel')
const extension = require('extensionizer')
const LocalStorageStore = require('obs-store/lib/localStorage')
const LocalStore = require('./lib/local-store')
const storeTransform = require('obs-store/lib/transform')
const asStream = require('obs-store/lib/asStream')
const ExtensionPlatform = require('./platforms/extension')
@ -44,6 +46,8 @@ let openMetamaskTabsIDs = {}
// state persistence
const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
const localStore = new LocalStore()
let versionedData
// initialization flow
initialize().catch(log.error)
@ -64,12 +68,23 @@ async function initialize () {
async function loadStateFromPersistence () {
// migrations
const migrator = new Migrator({ migrations })
// read from disk
let versionedData = diskStore.getState() || migrator.generateInitialState(firstTimeState)
// first from preferred, async API:
versionedData = (await localStore.get()) ||
diskStore.getState() ||
migrator.generateInitialState(firstTimeState)
// migrate data
versionedData = await migrator.migrateData(versionedData)
if (!versionedData) {
throw new Error('MetaMask - migrator returned undefined')
}
// write to disk
if (localStore.isSupported) localStore.set(versionedData)
diskStore.putState(versionedData)
// return just the data
return versionedData.data
}
@ -102,16 +117,30 @@ function setupController (initState) {
// setup state persistence
pump(
asStream(controller.store),
debounce(1000),
storeTransform(versionifyData),
asStream(diskStore)
storeTransform(syncDataWithExtension),
asStream(diskStore),
(error) => {
log.error('pump hit error', error)
}
)
function versionifyData (state) {
const versionedData = diskStore.getState()
versionedData.data = state
return versionedData
}
function syncDataWithExtension(state) {
if (localStore.isSupported) {
localStore.set(state)
.catch((err) => {
log.error('error setting state in local store:', err)
})
}
return state
}
//
// connect to other contexts
//

@ -0,0 +1,62 @@
// We should not rely on local storage in an extension!
// We should use this instead!
// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage/local
const extension = require('extensionizer')
module.exports = class ExtensionStore {
constructor() {
this.isSupported = !!(extension.storage.local)
if (!this.isSupported) {
log.error('Storage local API not available.')
}
}
async get() {
if (!this.isSupported) return undefined
const result = await this._get()
// extension.storage.local always returns an obj
// if the object is empty, treat it as undefined
if (isEmpty(result)) {
return undefined
} else {
return result
}
}
async set(state) {
return this._set(state)
}
_get() {
const local = extension.storage.local
return new Promise((resolve, reject) => {
local.get(null, (result) => {
const err = extension.runtime.lastError
if (err) {
reject(err)
} else {
resolve(result)
}
})
})
}
_set(obj) {
const local = extension.storage.local
return new Promise((resolve, reject) => {
local.set(obj, () => {
const err = extension.runtime.lastError
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
}
function isEmpty(obj) {
return Object.keys(obj).length === 0
}

@ -4,7 +4,7 @@ const {
BnMultiplyByFraction,
bnToHex,
} = require('./util')
const addHexPrefix = require('ethereumjs-util').addHexPrefix
const { addHexPrefix, isValidAddress } = require('ethereumjs-util')
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
/*
@ -113,12 +113,14 @@ module.exports = class TxGasUtil {
}
}
validateRecipient (txParams) {
if (txParams.to === '0x') {
if (txParams.to === '0x' || txParams.to === null ) {
if (txParams.data) {
delete txParams.to
} else {
throw new Error('Invalid recipient address')
}
} else if ( txParams.to !== undefined && !isValidAddress(txParams.to) ) {
throw new Error('Invalid recipient address')
}
return txParams
}

@ -1,18 +1,21 @@
const fs = require('fs')
const path = require('path')
const promisify = require('pify')
const statesPath = path.join(__dirname, 'states')
const stateNames = fs.readdirSync(statesPath)
start().catch(console.error)
const states = stateNames.reduce((result, stateFileName) => {
const statePath = path.join(__dirname, 'states', stateFileName)
const stateFile = fs.readFileSync(statePath).toString()
const state = JSON.parse(stateFile)
result[stateFileName.split('.')[0].replace(/-/g, ' ', 'g')] = state
return result
}, {})
const result = `module.exports = ${JSON.stringify(states)}`
const statesJsonPath = path.join(__dirname, 'states.js')
fs.writeFileSync(statesJsonPath, result)
async function start () {
const statesPath = path.join(__dirname, 'states')
const stateFilesNames = await promisify(fs.readdir)(statesPath)
const states = {}
await Promise.all(stateFilesNames.map(async (stateFileName) => {
const stateFilePath = path.join(__dirname, 'states', stateFileName)
const stateFileContent = await promisify(fs.readFile)(stateFilePath, 'utf8')
const state = JSON.parse(stateFileContent)
const stateName = stateFileName.split('.')[0].replace(/-/g, ' ', 'g')
states[stateName] = state
}))
const generatedFileContent = `module.exports = ${JSON.stringify(states)}`
const generatedFilePath = path.join(__dirname, 'states.js')
await promisify(fs.writeFile)(generatedFilePath, generatedFileContent)
}

@ -15,16 +15,16 @@
const extend = require('xtend')
const render = require('react-dom').render
const h = require('react-hyperscript')
const Root = require('./ui/app/root')
const configureStore = require('./ui/app/store')
const actions = require('./ui/app/actions')
const states = require('./development/states')
const backGroundConnectionModifiers = require('./development/backGroundConnectionModifiers')
const Selector = require('./development/selector')
const MetamaskController = require('./app/scripts/metamask-controller')
const firstTimeState = require('./app/scripts/first-time-state')
const ExtensionPlatform = require('./app/scripts/platforms/extension')
const extension = require('./development/mockExtension')
const Root = require('../ui/app/root')
const configureStore = require('../ui/app/store')
const actions = require('../ui/app/actions')
const states = require('./states')
const backGroundConnectionModifiers = require('./backGroundConnectionModifiers')
const Selector = require('./selector')
const MetamaskController = require('../app/scripts/metamask-controller')
const firstTimeState = require('../app/scripts/first-time-state')
const ExtensionPlatform = require('../app/scripts/platforms/extension')
const extension = require('./mockExtension')
const noop = function () {}
const log = require('loglevel')
@ -51,7 +51,7 @@ function updateQueryParams(newView) {
// CSS
//
const MetaMaskUiCss = require('./ui/css')
const MetaMaskUiCss = require('../ui/css')
const injectCss = require('inject-css')
//

@ -1,4 +1,4 @@
const { promisify } = require('util')
const promisify = require('pify')
const fs = require('fs')
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)

@ -17,10 +17,10 @@
const render = require('react-dom').render
const h = require('react-hyperscript')
const Root = require('./ui/app/root')
const configureStore = require('./development/uiStore')
const states = require('./development/states')
const Selector = require('./development/selector')
const Root = require('../ui/app/root')
const configureStore = require('./uiStore')
const states = require('./states')
const Selector = require('./selector')
// logger
const log = require('loglevel')
@ -35,7 +35,7 @@ const firstState = states[selectedView]
updateQueryParams(selectedView)
// CSS
const MetaMaskUiCss = require('./ui/css')
const MetaMaskUiCss = require('../ui/css')
const injectCss = require('inject-css')

@ -0,0 +1,18 @@
# MetaMask Translation Guide
The MetaMask browser extension supports new translations added in the form of new locales files added in `app/_locales`.
- [The MDN Guide to Internationalizing Extensions](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Internationalization)
## Adding a new Language
Each supported language is represented by a folder in `app/_locales` whose name is that language's subtag ([look up a language subtag using this tool](https://r12a.github.io/app-subtags/)).
Inside that folder there should be a `messages.json` file that follows the specified format. An easy way to start your translation is to first duplicate `app/_locales/en/messages.json` (the english translation), and then update the `message` key for each in-app message.
That's it! When MetaMask is loaded on a computer with that language set as the system language, they will see your translation instead of the default one.
## Testing
To verify that your translation works, you will need to [build a local copy](https://github.com/MetaMask/metamask-extension#building-locally) of MetaMask.

@ -2,6 +2,7 @@ import EventEmitter from 'events'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import classnames from 'classnames'
import {createNewVaultAndKeychain} from '../../../../ui/app/actions'
import LoadingScreen from './loading-screen'
import Breadcrumbs from './breadcrumbs'
@ -14,6 +15,7 @@ class CreatePasswordScreen extends Component {
goToImportWithSeedPhrase: PropTypes.func.isRequired,
goToImportAccount: PropTypes.func.isRequired,
next: PropTypes.func.isRequired,
isMascara: PropTypes.bool.isRequired,
}
state = {
@ -53,14 +55,17 @@ class CreatePasswordScreen extends Component {
}
render () {
const { isLoading, goToImportWithSeedPhrase } = this.props
const { isLoading, goToImportWithSeedPhrase, isMascara } = this.props
return isLoading
? <LoadingScreen loadingMessage="Creating your new account" />
: (
<div>
<div className="first-view-main">
<div className="mascara-info">
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
<div className={classnames({
'first-view-main': !isMascara,
'first-view-main__mascara': isMascara,
})}>
{isMascara && <div className="mascara-info first-view-phone-invisible">
<Mascot
animationEventEmitter={this.animationEventEmitter}
width="225"
@ -72,7 +77,7 @@ class CreatePasswordScreen extends Component {
<div className="info">
It allows you to hold ether & tokens, and interact with decentralized applications.
</div>
</div>
</div>}
<div className="create-password">
<div className="create-password__title">
Create Password
@ -127,7 +132,7 @@ class CreatePasswordScreen extends Component {
}
export default connect(
({ appState: { isLoading } }) => ({ isLoading }),
({ appState: { isLoading }, metamask: { isMascara } }) => ({ isLoading, isMascara }),
dispatch => ({
createAccount: password => dispatch(createNewVaultAndKeychain(password)),
})

@ -1,3 +1,10 @@
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'), url('/fonts/Roboto/Roboto-Regular.ttf') format('truetype');
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
.first-time-flow {
width: 100vw;
@ -6,21 +13,52 @@
display: flex;
justify-content: center;
flex: 1 0 auto;
font-weight: 400;
font-family: Roboto;
}
.alpha-warning {
.alpha-warning,
.alpha-warning-welcome-screen {
background: #f7861c;
color: #fff;
line-height: 2em;
padding-left: 10vw;
}
.first-view-main {
.alpha-warning-welcome-screen {
padding-left: 0;
text-align: center;
}
.first-view-main-wrapper {
display: flex;
width: 100%;
padding-left: 10vw;
}
.first-view-main,
.first-view-main__mascara {
display: flex;
flex-direction: row-reverse;
justify-content: center;
}
.first-view-main__mascara {
justify-content: space-between;
}
@media screen and (max-width: 575px) {
.first-view-main-wrapper {
padding: 0;
}
}
@media screen and (min-width: 1281px) {
.first-view-main {
width: 62vw;
}
}
.mascara-info {
display: flex;
flex-flow: column;
@ -81,6 +119,45 @@
width: initial !important;
}
.alpha-warning,
.alpha-warning-welcome-screen {
line-height: 1em;
padding: 8px 12px;
}
.first-view-main {
height: 100%;
flex-direction: column;
align-items: center;
justify-content: space-evenly;
margin-top: 12px;
}
.first-view-main .create-password {
margin-top: 0px;
}
.mascara-info {
margin-top: 0px;
width: 100%;
align-items: center;
}
.mascara-info .info {
text-align: center;
font-size: 16px;
margin: 0 10px;
padding-left: 0px;
}
.mascara-info :first-child {
align-self: center;
}
.first-view-phone-invisible {
display: none;
}
.first-time-flow__input {
width: initial !important;
font-size: 14px !important;
@ -180,11 +257,11 @@
color: #1B344D;
font-size: 16px;
line-height: 23px;
font-family: Montserrat UltraLight;
font-family: Roboto;
}
.buy-ether__small-body-text {
font-family: Montserrat UltraLight;
font-family: Roboto;
height: 14px;
color: #757575;
font-size: 12px;
@ -212,7 +289,7 @@
height: 334px;
overflow-y: auto;
color: #757575;
font-family: Montserrat UltraLight;
font-family: Roboto;
font-size: 12px;
line-height: 15px;
text-align: justify;
@ -237,7 +314,7 @@
color: #5B5D67;
font-size: 16px;
line-height: 23px;
font-family: Montserrat UltraLight;
font-family: Roboto;
}
.backup-phrase__secret {
@ -255,7 +332,7 @@
.backup-phrase__secret-words {
width: 310px;
color: #5B5D67;
font-family: Montserrat Light;
font-family: Roboto;
font-size: 20px;
line-height: 26px;
text-align: center;
@ -284,7 +361,7 @@
background: none;
box-shadow: none;
color: #FFFFFF;
font-family: Montserrat Regular;
font-family: Roboto;
font-size: 12px;
font-weight: bold;
line-height: 15px;
@ -338,7 +415,7 @@ button.backup-phrase__reveal-button:hover {
.backup-phrase__confirm-seed-option {
color: #5B5D67;
font-family: Montserrat Light;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
background-color: #E7E7E7;
@ -360,7 +437,7 @@ button.backup-phrase__confirm-seed-option:hover {
.import-account__faq-link {
font-size: 18px;
line-height: 23px;
font-family: Montserrat Light;
font-family: Roboto;
}
.import-account__selector-label {
@ -375,7 +452,7 @@ button.backup-phrase__confirm-seed-option:hover {
background-color: #FFFFFF;
margin-top: 14px;
color: #5B5D67;
font-family: Montserrat Light;
font-family: Roboto;
font-size: 18px;
line-height: 23px;
padding: 14px 21px;
@ -390,7 +467,7 @@ button.backup-phrase__confirm-seed-option:hover {
font-size: 18px;
line-height: 23px;
margin-top: 21px;
font-family: Montserrat UltraLight;
font-family: Roboto;
}
.import-account__input-wrapper {
@ -436,7 +513,7 @@ button.backup-phrase__confirm-seed-option:hover {
border: 1px solid #1B344D;
border-radius: 4px;
color: #1B344D;
font-family: Montserrat Light;
font-family: Roboto;
font-size: 18px;
display: flex;
flex-flow: column nowrap;
@ -453,7 +530,7 @@ button.backup-phrase__confirm-seed-option:hover {
.import-account__file-name {
color: #000000;
font-family: Montserrat Light;
font-family: Roboto;
font-size: 18px;
line-height: 23px;
margin-left: 22px;
@ -474,7 +551,7 @@ button.backup-phrase__confirm-seed-option:hover {
.buy-ether__content-headline {
color: #1B344D;
font-family: Montserrat Light;
font-family: Roboto;
font-size: 18px;
line-height: 23px;
}
@ -503,7 +580,7 @@ button.backup-phrase__confirm-seed-option:hover {
align-items: center;
padding: 20px 0;
color: #9B9B9B;
font-family: Montserrat Light;
font-family: Roboto;
font-size: 14px;
line-height: 18px;
cursor: pointer;
@ -538,7 +615,7 @@ button.backup-phrase__confirm-seed-option:hover {
.buy-ether__button-separator-text {
font-size: 20px;
line-height: 26px;
font-family: Montserrat Light;
font-family: Roboto;
margin: 35px 0 14px 30px;
display: flex;
flex-flow: column nowrap;
@ -550,7 +627,7 @@ button.backup-phrase__confirm-seed-option:hover {
color: #1B344D !important;
font-size: 14px !important;
line-height: 18px !important;
font-family: Montserrat UltraLight !important;
font-family: Roboto;
}
.buy-ether__action-content-wrapper {
@ -584,6 +661,7 @@ button.backup-phrase__confirm-seed-option:hover {
color: #FFFFFF;
font-size: 20px;
font-weight: 500;
font-family: Roboto;
line-height: 26px;
text-align: center;
text-transform: uppercase;
@ -608,7 +686,7 @@ button.first-time-flow__button:hover {
color: #1B344D;
font-size: 20px;
line-height: 26px;
font-family: Montserrat Light;
font-family: Roboto;
text-align: center;
margin: 35px 0 14px;
background-color: transparent;
@ -660,7 +738,7 @@ button.first-time-flow__button--tertiary:hover {
font-size: 20px;
line-height: 26px;
text-align: center;
font-family: Montserrat UltraLight;
font-family: Roboto;
}
.icon {
@ -708,7 +786,7 @@ button.first-time-flow__button--tertiary:hover {
.shapeshift-form__deposit-instruction {
color: #757575;
color: rgba(0, 0, 0, 0.45);
font-family: Montserrat Light;
font-family: Roboto;
font-weight: 300;
line-height: 19px;
padding-bottom: 6px;
@ -725,7 +803,7 @@ button.first-time-flow__button--tertiary:hover {
width: 100%;
height: 45px;
line-height: 44px;
font-family: Montserrat Light;
font-family: Roboto;
}
.shapeshift-form__address-input-label {
@ -734,7 +812,7 @@ button.first-time-flow__button--tertiary:hover {
font-weight: 500;
line-height: 18px;
padding-bottom: 6px;
font-family: Montserrat Light;
font-family: Roboto;
}
.shapeshift-form__address-input {
@ -753,7 +831,7 @@ button.first-time-flow__button--tertiary:hover {
.shapeshift-form__address-input-error-message {
color: #FF001F;
font-family: Montserrat Light;
font-family: Roboto;
font-size: 12px;
height: 24px;
line-height: 18px;
@ -763,7 +841,7 @@ button.first-time-flow__button--tertiary:hover {
display: flex;
flex-flow: row wrap;
color: #9B9B9B;
font-family: Montserrat Light;
font-family: Roboto;
font-size: 10px;
line-height: 16px;
}

@ -140,6 +140,19 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () {
// check seed
var seedBox = document.querySelector('textarea.twelve-word-phrase')
var seed = seedBox.value.trim()
// true if the string has more than a space between words.
if (seed.split(' ').length > 1) {
this.warning = 'there can only be a space between words'
this.props.dispatch(actions.displayWarning(this.warning))
return
}
// true if seed contains a character that is not between a-z or a space
if (!seed.match(/^[a-z ]+$/)) {
this.warning = 'seed words only have lowercase characters'
this.props.dispatch(actions.displayWarning(this.warning))
return
}
if (seed.split(' ').length !== 12) {
this.warning = 'seed phrases are 12 words long'
this.props.dispatch(actions.displayWarning(this.warning))

10
package-lock.json generated

@ -3624,6 +3624,16 @@
"object-assign": "4.1.1"
}
},
"cross-env": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.1.4.tgz",
"integrity": "sha512-Mx8mw6JWhfpYoEk7PGvHxJMLQwQHORAs8+2bX+C1lGQ4h3GkDb1zbzC2Nw85YH9ZQMlO0BHZxMacgrfPmMFxbg==",
"dev": true,
"requires": {
"cross-spawn": "5.1.0",
"is-windows": "1.0.2"
}
},
"cross-spawn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",

@ -6,15 +6,15 @@
"scripts": {
"start": "npm run dev",
"dev": "gulp dev --debug",
"ui": "npm run test:flat:build:states && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"ui": "npm run test:flat:build:states && beefy development/ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"mock": "beefy development/mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
"watch": "mocha watch --recursive \"test/unit/**/*.js\"",
"mascara": "gulp build && METAMASK_DEBUG=true node ./mascara/example/server",
"mascara": "gulp build && cross-env METAMASK_DEBUG=true node ./mascara/example/server",
"dist": "npm run dist:clear && npm install && gulp dist",
"dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect",
"test": "npm run lint && npm run test:coverage && npm run test:integration",
"test:unit": "METAMASK_ENV=test mocha --exit --require babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"",
"test:single": "METAMASK_ENV=test mocha --require test/helper.js",
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"",
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
"test:integration:build": "gulp build:scss",
"test:e2e": "METAMASK_ENV=test mocha test/e2e/metamask.spec --recursive || true",
@ -24,7 +24,7 @@
"test:flat:build": "npm run test:flat:build:ui && npm run test:flat:build:tests",
"test:flat:build:tests": "node test/integration/index.js",
"test:flat:build:states": "node development/genStates.js",
"test:flat:build:ui": "npm run test:flat:build:states && browserify ./mock-dev.js -o ./development/bundle.js",
"test:flat:build:ui": "npm run test:flat:build:states && browserify ./development/mock-dev.js -o ./development/bundle.js",
"test:mascara": "npm run test:mascara:build && karma start test/mascara.conf.js",
"test:mascara:build": "mkdir -p dist/mascara && npm run test:mascara:build:ui && npm run test:mascara:build:background && npm run test:mascara:build:tests",
"test:mascara:build:ui": "browserify mascara/test/test-ui.js -o dist/mascara/ui.js",
@ -73,6 +73,7 @@
"clone": "^2.1.1",
"copy-to-clipboard": "^3.0.8",
"debounce": "^1.0.0",
"debounce-stream": "^2.0.0",
"deep-extend": "^0.5.0",
"detect-node": "^2.0.3",
"disc": "^1.3.2",
@ -195,6 +196,7 @@
"chromedriver": "^2.34.1",
"compression": "^1.7.1",
"coveralls": "^3.0.0",
"cross-env": "^5.1.4",
"deep-freeze-strict": "^1.1.1",
"del": "^3.0.0",
"envify": "^4.0.0",

@ -46,7 +46,9 @@ module.exports = function(config) {
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome', 'Firefox'],
browsers: process.env.browsers ?
JSON.parse(process.env.browsers)
: ['Chrome', 'Firefox'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits

@ -1,4 +1,9 @@
const reactTriggerChange = require('react-trigger-change')
const {
timeout,
queryAsync,
findAsync,
} = require('../../lib/util')
QUnit.module('Add token flow')
@ -13,74 +18,60 @@ QUnit.test('successful add token flow', (assert) => {
})
async function runAddTokenFlowTest (assert, done) {
const selectState = $('select')
const selectState = await queryAsync($, 'select')
selectState.val('add token')
reactTriggerChange(selectState[0])
await timeout(2000)
// Check that no tokens have been added
assert.ok($('.token-list-item').length === 0, 'no tokens added')
// Go to Add Token screen
let addTokenButton = $('button.btn-clear.wallet-view__add-token-button')
let addTokenButton = await queryAsync($, 'button.btn-clear.wallet-view__add-token-button')
assert.ok(addTokenButton[0], 'add token button present')
addTokenButton[0].click()
await timeout(1000)
// Verify Add Token screen
let addTokenWrapper = $('.add-token__wrapper')
let addTokenWrapper = await queryAsync($, '.add-token__wrapper')
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
let addTokenTitle = $('.add-token__title')
let addTokenTitle = await queryAsync($, '.add-token__title')
assert.equal(addTokenTitle[0].textContent, 'Add Token', 'add token title is correct')
// Cancel Add Token
const cancelAddTokenButton = $('button.btn-cancel.add-token__button')
const cancelAddTokenButton = await queryAsync($, 'button.btn-cancel.add-token__button')
assert.ok(cancelAddTokenButton[0], 'cancel add token button present')
cancelAddTokenButton.click()
await timeout(1000)
assert.ok($('.wallet-view')[0], 'cancelled and returned to account detail wallet view')
// Return to Add Token Screen
addTokenButton = $('button.btn-clear.wallet-view__add-token-button')
addTokenButton = await queryAsync($, 'button.btn-clear.wallet-view__add-token-button')
assert.ok(addTokenButton[0], 'add token button present')
addTokenButton[0].click()
await timeout(1000)
// Verify Add Token Screen
addTokenWrapper = $('.add-token__wrapper')
addTokenTitle = $('.add-token__title')
addTokenWrapper = await queryAsync($, '.add-token__wrapper')
addTokenTitle = await queryAsync($, '.add-token__title')
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
assert.equal(addTokenTitle[0].textContent, 'Add Token', 'add token title is correct')
// Search for token
const searchInput = $('input.add-token__input')
const searchInput = await queryAsync($, 'input.add-token__input')
searchInput.val('a')
reactTriggerChange(searchInput[0])
await timeout()
// Click token to add
const tokenWrapper = $('div.add-token__token-wrapper')
const tokenWrapper = await queryAsync($, 'div.add-token__token-wrapper')
assert.ok(tokenWrapper[0], 'token found')
const tokenImageProp = tokenWrapper.find('.add-token__token-icon').css('background-image')
const tokenImageUrl = tokenImageProp.slice(5, -2)
tokenWrapper[0].click()
await timeout()
// Click Next button
let nextButton = $('button.btn-clear.add-token__button')
let nextButton = await queryAsync($, 'button.btn-clear.add-token__button')
assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
nextButton[0].click()
await timeout()
// Confirm Add token
assert.equal(
$('.add-token__description')[0].textContent,
@ -90,47 +81,35 @@ async function runAddTokenFlowTest (assert, done) {
assert.ok($('button.btn-clear.add-token__button')[0], 'confirm add token button found')
$('button.btn-clear.add-token__button')[0].click()
await timeout(2000)
// Verify added token image
let heroBalance = $('.hero-balance')
let heroBalance = await queryAsync($, '.hero-balance')
assert.ok(heroBalance, 'rendered hero balance')
assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added')
// Return to Add Token Screen
addTokenButton = $('button.btn-clear.wallet-view__add-token-button')
addTokenButton = await queryAsync($, 'button.btn-clear.wallet-view__add-token-button')
assert.ok(addTokenButton[0], 'add token button present')
addTokenButton[0].click()
await timeout(1000)
const addCustom = $('.add-token__add-custom')
const addCustom = await queryAsync($, '.add-token__add-custom')
assert.ok(addCustom[0], 'add custom token button present')
addCustom[0].click()
await timeout()
// Input token contract address
const customInput = $('input.add-token__add-custom-input')
const customInput = await queryAsync($, 'input.add-token__add-custom-input')
customInput.val('0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c')
reactTriggerChange(customInput[0])
await timeout(1000)
// Click Next button
nextButton = $('button.btn-clear.add-token__button')
nextButton = await queryAsync($, 'button.btn-clear.add-token__button')
assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
nextButton[0].click()
await timeout(1000)
// Verify symbol length error since contract address won't return symbol
const errorMessage = $('.add-token__add-custom-error-message')
const errorMessage = await queryAsync($, '.add-token__add-custom-error-message')
assert.ok(errorMessage[0], 'error rendered')
$('button.btn-cancel.add-token__button')[0].click()
await timeout(2000)
// // Confirm Add token
// assert.equal(
// $('.add-token__description')[0].textContent,
@ -141,13 +120,7 @@ async function runAddTokenFlowTest (assert, done) {
// $('button.btn-clear.add-token__button')[0].click()
// // Verify added token image
// heroBalance = $('.hero-balance')
// heroBalance = await queryAsync($, '.hero-balance')
// assert.ok(heroBalance, 'rendered hero balance')
// assert.ok(heroBalance.find('.identicon')[0], 'token added')
}
function timeout (time) {
return new Promise((resolve, reject) => {
setTimeout(resolve, time || 1500)
})
}

@ -1,5 +1,9 @@
const reactTriggerChange = require('react-trigger-change')
const {
timeout,
queryAsync,
findAsync,
} = require('../../lib/util')
const PASSWORD = 'password123'
QUnit.module('confirm sig requests')
@ -13,55 +17,41 @@ QUnit.test('successful confirmation of sig requests', (assert) => {
})
async function runConfirmSigRequestsTest(assert, done) {
let selectState = $('select')
let selectState = await queryAsync($, 'select')
selectState.val('confirm sig requests')
reactTriggerChange(selectState[0])
await timeout(2000)
let confirmSigHeadline = $('.request-signature__headline')
let confirmSigHeadline = await queryAsync($, '.request-signature__headline')
assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested')
let confirmSigRowValue = $('.request-signature__row-value')
let confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.ok(confirmSigRowValue[0].textContent.match(/^\#\sTerms\sof\sUse/))
let confirmSigSignButton = $('.request-signature__footer__sign-button')
let confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button')
confirmSigSignButton[0].click()
await timeout(2000)
confirmSigHeadline = $('.request-signature__headline')
confirmSigHeadline = await queryAsync($, '.request-signature__headline')
assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested')
let confirmSigMessage = $('.request-signature__notice')
let confirmSigMessage = await queryAsync($, '.request-signature__notice')
assert.ok(confirmSigMessage[0].textContent.match(/^Signing\sthis\smessage/))
confirmSigRowValue = $('.request-signature__row-value')
confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0')
confirmSigSignButton = $('.request-signature__footer__sign-button')
confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button')
confirmSigSignButton[0].click()
await timeout(2000)
confirmSigHeadline = $('.request-signature__headline')
confirmSigHeadline = await queryAsync($, '.request-signature__headline')
assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested')
confirmSigRowValue = $('.request-signature__row-value')
confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!')
assert.equal(confirmSigRowValue[1].textContent, '1337')
confirmSigSignButton = $('.request-signature__footer__sign-button')
confirmSigSignButton = await queryAsync($, '.request-signature__footer__sign-button')
confirmSigSignButton[0].click()
await timeout(2000)
const txView = $('.tx-view')
const txView = await queryAsync($, '.tx-view')
assert.ok(txView[0], 'Should return to the account details screen after confirming')
}
function timeout (time) {
return new Promise((resolve, reject) => {
setTimeout(resolve, time || 1500)
})
}

@ -1,6 +1,10 @@
const reactTriggerChange = require('react-trigger-change')
const PASSWORD = 'password123'
const runMascaraFirstTimeTest = require('./mascara-first-time')
const {
timeout,
findAsync,
} = require('../../lib/util')
QUnit.module('first time usage')
@ -21,20 +25,19 @@ async function runFirstTimeUsageTest(assert, done) {
selectState.val('first time')
reactTriggerChange(selectState[0])
await timeout(2000)
const app = $('#app-content')
// recurse notices
while (true) {
const button = app.find('button')
const button = await findAsync(app, 'button')
if (button.html() === 'Accept') {
// still notices to accept
const termsPage = app.find('.markdown')[0]
const termsPageRaw = await findAsync(app, '.markdown')
const termsPage = (await findAsync(app, '.markdown'))[0]
console.log('termsPageRaw', termsPageRaw)
termsPage.scrollTop = termsPage.scrollHeight
await timeout()
console.log('Clearing notice')
button.click()
await timeout()
} else {
// exit loop
console.log('No more notices...')
@ -42,97 +45,68 @@ async function runFirstTimeUsageTest(assert, done) {
}
}
await timeout()
// Scroll through terms
const title = app.find('h1')[0]
const title = (await findAsync(app, 'h1'))[0]
assert.equal(title.textContent, 'MetaMask', 'title screen')
// enter password
const pwBox = app.find('#password-box')[0]
const confBox = app.find('#password-box-confirm')[0]
const pwBox = (await findAsync(app, '#password-box'))[0]
const confBox = (await findAsync(app, '#password-box-confirm'))[0]
pwBox.value = PASSWORD
confBox.value = PASSWORD
await timeout()
// create vault
const createButton = app.find('button.primary')[0]
const createButton = (await findAsync(app, 'button.primary'))[0]
createButton.click()
await timeout(3000)
const created = app.find('h3')[0]
await timeout()
const created = (await findAsync(app, 'h3'))[0]
assert.equal(created.textContent, 'Vault Created', 'Vault created screen')
// Agree button
const button = app.find('button')[0]
const button = (await findAsync(app, 'button'))[0]
assert.ok(button, 'button present')
button.click()
await timeout(1000)
const detail = app.find('.account-detail-section')[0]
const detail = (await findAsync(app, '.account-detail-section'))[0]
assert.ok(detail, 'Account detail section loaded.')
const sandwich = app.find('.sandwich-expando')[0]
const sandwich = (await findAsync(app, '.sandwich-expando'))[0]
sandwich.click()
await timeout()
const menu = app.find('.menu-droppo')[0]
const menu = (await findAsync(app, '.menu-droppo'))[0]
const children = menu.children
const logout = children[2]
assert.ok(logout, 'Lock menu item found')
logout.click()
await timeout(1000)
const pwBox2 = app.find('#password-box')[0]
const pwBox2 = (await findAsync(app, '#password-box'))[0]
pwBox2.value = PASSWORD
const createButton2 = app.find('button.primary')[0]
const createButton2 = (await findAsync(app, 'button.primary'))[0]
createButton2.click()
await timeout(1000)
const detail2 = app.find('.account-detail-section')[0]
const detail2 = (await findAsync(app, '.account-detail-section'))[0]
assert.ok(detail2, 'Account detail section loaded again.')
await timeout()
// open account settings dropdown
const qrButton = app.find('.fa.fa-ellipsis-h')[0]
const qrButton = (await findAsync(app, '.fa.fa-ellipsis-h'))[0]
qrButton.click()
await timeout(1000)
// qr code item
const qrButton2 = app.find('.dropdown-menu-item')[1]
const qrButton2 = (await findAsync(app, '.dropdown-menu-item'))[1]
qrButton2.click()
await timeout(1000)
const qrHeader = app.find('.qr-header')[0]
const qrContainer = app.find('#qr-container')[0]
const qrHeader = (await findAsync(app, '.qr-header'))[0]
const qrContainer = (await findAsync(app, '#qr-container'))[0]
assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.')
assert.ok(qrContainer, 'QR Container found')
await timeout()
const networkMenu = app.find('.network-indicator')[0]
const networkMenu = (await findAsync(app, '.network-indicator'))[0]
networkMenu.click()
await timeout()
const networkMenu2 = app.find('.network-indicator')[0]
const networkMenu2 = (await findAsync(app, '.network-indicator'))[0]
const children2 = networkMenu2.children
children2.length[3]
assert.ok(children2, 'All network options present')
}
function timeout (time) {
return new Promise((resolve, reject) => {
setTimeout(resolve, time || 1500)
})
}

@ -1,119 +1,95 @@
const PASSWORD = 'password123'
const reactTriggerChange = require('react-trigger-change')
const {
timeout,
findAsync,
queryAsync,
} = require('../../lib/util')
async function runFirstTimeUsageTest (assert, done) {
await timeout(4000)
const app = $('#app-content')
const app = await queryAsync($, '#app-content')
await skipNotices(app)
await timeout()
// Scroll through terms
const title = app.find('.create-password__title').text()
const title = (await findAsync(app, '.create-password__title')).text()
assert.equal(title, 'Create Password', 'create password screen')
// enter password
const pwBox = app.find('.first-time-flow__input')[0]
const confBox = app.find('.first-time-flow__input')[1]
const pwBox = (await findAsync(app, '.first-time-flow__input'))[0]
const confBox = (await findAsync(app, '.first-time-flow__input'))[1]
pwBox.value = PASSWORD
confBox.value = PASSWORD
reactTriggerChange(pwBox)
reactTriggerChange(confBox)
await timeout()
// Create Password
const createButton = app.find('button.first-time-flow__button')[0]
const createButton = (await findAsync(app, 'button.first-time-flow__button'))[0]
createButton.click()
await timeout(3000)
const created = app.find('.unique-image__title')[0]
const created = (await findAsync(app, '.unique-image__title'))[0]
assert.equal(created.textContent, 'Your unique account image', 'unique image screen')
// Agree button
let button = app.find('button')[0]
let button = (await findAsync(app, 'button'))[0]
assert.ok(button, 'button present')
button.click()
await timeout(1000)
await skipNotices(app)
// secret backup phrase
const seedTitle = app.find('.backup-phrase__title')[0]
const seedTitle = (await findAsync(app, '.backup-phrase__title'))[0]
assert.equal(seedTitle.textContent, 'Secret Backup Phrase', 'seed phrase screen')
app.find('.backup-phrase__reveal-button').click()
await timeout(1000)
const seedPhrase = app.find('.backup-phrase__secret-words').text().split(' ')
app.find('.first-time-flow__button').click()
;(await findAsync(app, '.backup-phrase__reveal-button')).click()
const seedPhrase = (await findAsync(app, '.backup-phrase__secret-words')).text().split(' ')
;(await findAsync(app, '.first-time-flow__button')).click()
await timeout()
const selectPhrase = text => {
const option = $('.backup-phrase__confirm-seed-option')
.filter((i, d) => d.textContent === text)[0]
$(option).click()
}
await timeout(1000)
seedPhrase.forEach(sp => selectPhrase(sp))
app.find('.first-time-flow__button').click()
await timeout(1000)
;(await findAsync(app, '.first-time-flow__button')).click()
// Deposit Ether Screen
const buyEthTitle = app.find('.buy-ether__title')[0]
const buyEthTitle = (await findAsync(app, '.buy-ether__title'))[0]
assert.equal(buyEthTitle.textContent, 'Deposit Ether', 'deposit ether screen')
app.find('.buy-ether__do-it-later').click()
await timeout(1000)
;(await findAsync(app, '.buy-ether__do-it-later')).click()
const menu = app.find('.account-menu__icon')[0]
const menu = (await findAsync(app, '.account-menu__icon'))[0]
menu.click()
await timeout()
const lock = app.find('.account-menu__logout-button')[0]
const lock = (await findAsync(app, '.account-menu__logout-button'))[0]
assert.ok(lock, 'Lock menu item found')
lock.click()
await timeout(1000)
const pwBox2 = app.find('#password-box')[0]
const pwBox2 = (await findAsync(app, '#password-box'))[0]
pwBox2.value = PASSWORD
const createButton2 = app.find('button.primary')[0]
const createButton2 = (await findAsync(app, 'button.primary'))[0]
createButton2.click()
await timeout(1000)
const detail2 = app.find('.wallet-view')[0]
const detail2 = (await findAsync(app, '.wallet-view'))[0]
assert.ok(detail2, 'Account detail section loaded again.')
await timeout()
// open account settings dropdown
const qrButton = app.find('.wallet-view__details-button')[0]
const qrButton = (await findAsync(app, '.wallet-view__details-button'))[0]
qrButton.click()
await timeout(1000)
const qrHeader = app.find('.editable-label__value')[0]
const qrContainer = app.find('.qr-wrapper')[0]
const qrHeader = (await findAsync(app, '.editable-label__value'))[0]
const qrContainer = (await findAsync(app, '.qr-wrapper'))[0]
assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.')
assert.ok(qrContainer, 'QR Container found')
await timeout()
const networkMenu = app.find('.network-component')[0]
const networkMenu = (await findAsync(app, '.network-component'))[0]
networkMenu.click()
await timeout()
const networkMenu2 = app.find('.network-indicator')[0]
const networkMenu2 = (await findAsync(app, '.network-indicator'))[0]
const children2 = networkMenu2.children
children2.length[3]
assert.ok(children2, 'All network options present')
@ -121,18 +97,12 @@ async function runFirstTimeUsageTest (assert, done) {
module.exports = runFirstTimeUsageTest
function timeout (time) {
return new Promise((resolve, reject) => {
setTimeout(resolve, time || 1500)
})
}
async function skipNotices (app) {
while (true) {
const button = app.find('button')
const button = await findAsync(app, 'button')
if (button && button.html() === 'Accept') {
// still notices to accept
const termsPage = app.find('.markdown')[0]
const termsPage = (await findAsync(app, '.markdown'))[0]
if (!termsPage) {
break
}

@ -1,4 +1,9 @@
const reactTriggerChange = require('react-trigger-change')
const {
timeout,
queryAsync,
findAsync,
} = require('../../lib/util')
const PASSWORD = 'password123'
@ -18,83 +23,65 @@ global.ethQuery = {
async function runSendFlowTest(assert, done) {
console.log('*** start runSendFlowTest')
const selectState = $('select')
const selectState = await queryAsync($, 'select')
selectState.val('send new ui')
reactTriggerChange(selectState[0])
await timeout(2000)
const sendScreenButton = $('button.btn-clear.hero-balance-button')
const sendScreenButton = await queryAsync($, 'button.btn-clear.hero-balance-button')
assert.ok(sendScreenButton[1], 'send screen button present')
sendScreenButton[1].click()
await timeout(1000)
const sendTitle = $('.page-container__title')
const sendTitle = await queryAsync($, '.page-container__title')
assert.equal(sendTitle[0].textContent, 'Send ETH', 'Send screen title is correct')
const sendCopy = $('.page-container__subtitle')
const sendCopy = await queryAsync($, '.page-container__subtitle')
assert.equal(sendCopy[0].textContent, 'Only send ETH to an Ethereum address.', 'Send screen has copy')
const sendFromField = $('.send-v2__form-field')
const sendFromField = await queryAsync($, '.send-v2__form-field')
assert.ok(sendFromField[0], 'send screen has a from field')
let sendFromFieldItemAddress = $('.account-list-item__account-name')
let sendFromFieldItemAddress = await queryAsync($, '.account-list-item__account-name')
assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 4', 'send from field shows correct account name')
const sendFromFieldItem = $('.account-list-item')
const sendFromFieldItem = await queryAsync($, '.account-list-item')
sendFromFieldItem[0].click()
await timeout()
const sendFromDropdownList = $('.send-v2__from-dropdown__list')
// this seems to fail if the firefox window is not in focus...
const sendFromDropdownList = await queryAsync($, '.send-v2__from-dropdown__list')
assert.equal(sendFromDropdownList.children().length, 4, 'send from dropdown shows all accounts')
console.log(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! sendFromDropdownList.children()[1]`, sendFromDropdownList.children()[1]);
sendFromDropdownList.children()[1].click()
await timeout()
sendFromFieldItemAddress = $('.account-list-item__account-name')
console.log(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! sendFromFieldItemAddress[0]`, sendFromFieldItemAddress[0]);
sendFromFieldItemAddress = await queryAsync($, '.account-list-item__account-name')
assert.equal(sendFromFieldItemAddress[0].textContent, 'Send Account 2', 'send from field dropdown changes account name')
let sendToFieldInput = $('.send-v2__to-autocomplete__input')
let sendToFieldInput = await queryAsync($, '.send-v2__to-autocomplete__input')
sendToFieldInput[0].focus()
await timeout()
const sendToDropdownList = $('.send-v2__from-dropdown__list')
const sendToDropdownList = await queryAsync($, '.send-v2__from-dropdown__list')
assert.equal(sendToDropdownList.children().length, 5, 'send to dropdown shows all accounts and address book accounts')
sendToDropdownList.children()[2].click()
await timeout()
const sendToAccountAddress = sendToFieldInput.val()
assert.equal(sendToAccountAddress, '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', 'send to dropdown selects the correct address')
const sendAmountField = $('.send-v2__form-row:eq(2)')
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)')
sendAmountField.find('.currency-display')[0].click()
await timeout()
const sendAmountFieldInput = sendAmountField.find('input:text')
const sendAmountFieldInput = await findAsync(sendAmountField, 'input:text')
sendAmountFieldInput.val('5.1')
reactTriggerChange(sendAmountField.find('input')[0])
await timeout()
let errorMessage = $('.send-v2__error')
let errorMessage = await queryAsync($, '.send-v2__error')
assert.equal(errorMessage[0].textContent, 'Insufficient funds.', 'send should render an insufficient fund error message')
sendAmountFieldInput.val('2.0')
reactTriggerChange(sendAmountFieldInput[0])
await timeout()
errorMessage = $('.send-v2__error')
assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected')
const sendGasField = $('.send-v2__gas-fee-display')
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
assert.equal(
sendGasField.find('.currency-display__input-wrapper > input').val(),
'0.000198',
@ -106,120 +93,86 @@ async function runSendFlowTest(assert, done) {
'send gas field should show estimated gas total converted to USD'
)
const sendGasOpenCustomizeModalButton = $('.send-v2__sliders-icon-container'
)
const sendGasOpenCustomizeModalButton = await queryAsync($, '.send-v2__sliders-icon-container')
sendGasOpenCustomizeModalButton[0].click()
await timeout(1000)
const customizeGasModal = $('.send-v2__customize-gas')
const customizeGasModal = await queryAsync($, '.send-v2__customize-gas')
assert.ok(customizeGasModal[0], 'should render the customize gas modal')
const customizeGasPriceInput = $('.send-v2__gas-modal-card').first().find('input')
const customizeGasPriceInput = (await queryAsync($, '.send-v2__gas-modal-card')).first().find('input')
customizeGasPriceInput.val(50)
reactTriggerChange(customizeGasPriceInput[0])
const customizeGasLimitInput = $('.send-v2__gas-modal-card').last().find('input')
const customizeGasLimitInput = (await queryAsync($, '.send-v2__gas-modal-card')).last().find('input')
customizeGasLimitInput.val(60000)
reactTriggerChange(customizeGasLimitInput[0])
await timeout()
const customizeGasSaveButton = $('.send-v2__customize-gas__save')
const customizeGasSaveButton = await queryAsync($, '.send-v2__customize-gas__save')
customizeGasSaveButton[0].click()
await timeout()
assert.equal(
sendGasField.find('.currency-display__input-wrapper > input').val(),
(await findAsync(sendGasField, '.currency-display__input-wrapper > input')).val(),
'0.003',
'send gas field should show customized gas total'
)
assert.equal(
sendGasField.find('.currency-display__converted-value')[0].textContent,
(await findAsync(sendGasField, '.currency-display__converted-value'))[0].textContent,
'3.60 USD',
'send gas field should show customized gas total converted to USD'
)
const sendButton = $('button.btn-clear.page-container__footer-button')
const sendButton = await queryAsync($, 'button.btn-clear.page-container__footer-button')
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
sendButton[0].click()
await timeout(2000)
await timeout()
selectState.val('send edit')
reactTriggerChange(selectState[0])
await timeout(2000)
const confirmFromName = $('.confirm-screen-account-name').first()
const confirmFromName = (await queryAsync($, '.confirm-screen-account-name')).first()
assert.equal(confirmFromName[0].textContent, 'Send Account 2', 'confirm screen should show correct from name')
const confirmToName = $('.confirm-screen-account-name').last()
const confirmToName = (await queryAsync($, '.confirm-screen-account-name')).last()
assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name')
const confirmScreenRows = $('.confirm-screen-rows')
const confirmScreenRows = await queryAsync($, '.confirm-screen-rows')
const confirmScreenGas = confirmScreenRows.find('.confirm-screen-row-info')[2]
assert.equal(confirmScreenGas.textContent, '3.6 USD', 'confirm screen should show correct gas')
const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[3]
assert.equal(confirmScreenTotal.textContent, '2405.36 USD', 'confirm screen should show correct total')
const confirmScreenBackButton = $('.confirm-screen-back-button')
const confirmScreenBackButton = await queryAsync($, '.confirm-screen-back-button')
confirmScreenBackButton[0].click()
await timeout(1000)
const sendFromFieldItemInEdit = $('.account-list-item')
const sendFromFieldItemInEdit = await queryAsync($, '.account-list-item')
sendFromFieldItemInEdit[0].click()
await timeout()
const sendFromDropdownListInEdit = $('.send-v2__from-dropdown__list')
const sendFromDropdownListInEdit = await queryAsync($, '.send-v2__from-dropdown__list')
sendFromDropdownListInEdit.children()[2].click()
await timeout()
const sendToFieldInputInEdit = $('.send-v2__to-autocomplete__input')
const sendToFieldInputInEdit = await queryAsync($, '.send-v2__to-autocomplete__input')
sendToFieldInputInEdit[0].focus()
sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb')
await timeout()
const sendAmountFieldInEdit = $('.send-v2__form-row:eq(2)')
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)')
sendAmountFieldInEdit.find('.currency-display')[0].click()
await timeout()
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('input:text')
sendAmountFieldInputInEdit.val('1.0')
reactTriggerChange(sendAmountFieldInputInEdit[0])
await timeout()
const sendButtonInEdit = $('.btn-clear.page-container__footer-button')
const sendButtonInEdit = await queryAsync($, '.btn-clear.page-container__footer-button')
assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered')
sendButtonInEdit[0].click()
await timeout()
// TODO: Need a way to mock background so that we can test correct transition from editing to confirm
selectState.val('confirm new ui')
reactTriggerChange(selectState[0])
await timeout(2000)
const confirmScreenConfirmButton = $('.confirm-screen-confirm-button')
const confirmScreenConfirmButton = await queryAsync($, '.confirm-screen-confirm-button')
console.log(`+++++++++++++++++++++++++++++++= confirmScreenConfirmButton[0]`, confirmScreenConfirmButton[0]);
confirmScreenConfirmButton[0].click()
await timeout(2000)
const txView = $('.tx-view')
const txView = await queryAsync($, '.tx-view')
console.log(`++++++++++++++++++++++++++++++++ txView[0]`, txView[0]);
assert.ok(txView[0], 'Should return to the account details screen after confirming')
}
function timeout (time) {
return new Promise((resolve, reject) => {
setTimeout(resolve, time || 1500)
})
}

@ -0,0 +1,53 @@
module.exports = {
timeout,
queryAsync,
findAsync,
pollUntilTruthy,
}
function timeout (time) {
return new Promise((resolve, reject) => {
setTimeout(resolve, time || 1500)
})
}
async function findAsync(container, selector, opts) {
try {
return await pollUntilTruthy(() => {
const result = container.find(selector)
if (result.length > 0) return result
}, opts)
} catch (err) {
throw new Error(`Failed to find element within interval: "${selector}"`)
}
}
async function queryAsync(jQuery, selector, opts) {
try {
return await pollUntilTruthy(() => {
const result = jQuery(selector)
if (result.length > 0) return result
}, opts)
} catch (err) {
throw new Error(`Failed to find element within interval: "${selector}"`)
}
}
async function pollUntilTruthy(fn, opts = {}){
const pollingInterval = opts.pollingInterval || 100
const timeoutInterval = opts.timeoutInterval || 5000
const start = Date.now()
let result
while (!result) {
// check if timedout
const now = Date.now()
if ((now - start) > timeoutInterval) {
throw new Error(`pollUntilTruthy - failed to return truthy within interval`)
}
// check for result
result = fn()
// run again after timeout
await timeout(pollingInterval, timeoutInterval)
}
return result
}

@ -1,6 +1,6 @@
const assert = require('assert')
const versionBump = require('../../../development/version-bump')
const { promisify } = require('util')
const promisify = require('pify')
const fs = require('fs')
const readFile = promisify(fs.readFile)
const path = require('path')
@ -41,5 +41,3 @@ describe('version bumper', function () {
assert.ok(result.changelog.includes(expected))
})
})

@ -2,6 +2,7 @@ const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const t = require('../../../i18n')
import Select from 'react-select'
// Subviews
@ -9,8 +10,8 @@ const JsonImportView = require('./json.js')
const PrivateKeyImportView = require('./private-key.js')
const menuItems = [
'Private Key',
'JSON File',
t('privateKey'),
t('jsonFile'),
]
module.exports = connect(mapStateToProps)(AccountImportSubview)
@ -85,9 +86,9 @@ AccountImportSubview.prototype.renderImportView = function () {
const current = type || menuItems[0]
switch (current) {
case 'Private Key':
case t('privateKey'):
return h(PrivateKeyImportView)
case 'JSON File':
case t('jsonFile'):
return h(JsonImportView)
default:
return h(JsonImportView)

@ -4,6 +4,7 @@ const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../actions')
const FileInput = require('react-simple-file-input').default
const t = require('../../../i18n')
const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts'
@ -23,11 +24,11 @@ class JsonImportSubview extends Component {
return (
h('div.new-account-import-form__json', [
h('p', 'Used by a variety of different clients'),
h('p', t('usedByClients')),
h('a.warning', {
href: HELP_LINK,
target: '_blank',
}, 'File import not working? Click here!'),
}, t('fileImportFail')),
h(FileInput, {
readAs: 'text',
@ -42,7 +43,7 @@ class JsonImportSubview extends Component {
h('input.new-account-import-form__input-password', {
type: 'password',
placeholder: 'Enter password',
placeholder: t('enterPassword'),
id: 'json-password-box',
onKeyPress: this.createKeyringOnEnter.bind(this),
}),
@ -52,13 +53,13 @@ class JsonImportSubview extends Component {
h('button.new-account-create-form__button-cancel', {
onClick: () => this.props.goHome(),
}, [
'CANCEL',
t('cancel'),
]),
h('button.new-account-create-form__button-create', {
onClick: () => this.createNewKeychain(),
}, [
'IMPORT',
t('import'),
]),
]),
@ -90,7 +91,7 @@ class JsonImportSubview extends Component {
const { fileContents } = state
if (!fileContents) {
const message = 'You must select a file to import.'
const message = t('needImportFile')
return this.props.displayWarning(message)
}
@ -98,7 +99,7 @@ class JsonImportSubview extends Component {
const password = passwordInput.value
if (!password) {
const message = 'You must enter a password for the selected file.'
const message = t('needImportPassword')
return this.props.displayWarning(message)
}

@ -3,6 +3,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../actions')
const t = require('../../../i18n')
module.exports = connect(mapStateToProps, mapDispatchToProps)(PrivateKeyImportView)
@ -33,9 +34,9 @@ PrivateKeyImportView.prototype.render = function () {
return (
h('div.new-account-import-form__private-key', [
h('div.new-account-import-form__private-key-password-container', [
h('span.new-account-create-form__instruction', t('pastePrivateKey')),
h('span.new-account-import-form__instruction', 'Paste your private key string here:'),
h('div.new-account-import-form__private-key-password-container', [
h('input.new-account-import-form__input-password', {
type: 'password',
@ -47,16 +48,16 @@ PrivateKeyImportView.prototype.render = function () {
h('div.new-account-import-form__buttons', {}, [
h('button.new-account-create-form__button-cancel', {
h('button.new-account-create-form__button-cancel.allcaps', {
onClick: () => goHome(),
}, [
'CANCEL',
t('cancel'),
]),
h('button.new-account-create-form__button-create', {
h('button.new-account-create-form__button-create.allcaps', {
onClick: () => this.createNewKeychain(),
}, [
'IMPORT',
t('import'),
]),
]),

@ -2,6 +2,7 @@ const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const t = require('../../../i18n')
module.exports = connect(mapStateToProps)(SeedImportSubview)
@ -20,11 +21,10 @@ SeedImportSubview.prototype.render = function () {
style: {
},
}, [
`Paste your seed phrase here!`,
t('pasteSeed'),
h('textarea'),
h('br'),
h('button', 'Submit'),
h('button', t('submit')),
])
)
}

@ -3,6 +3,7 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const { connect } = require('react-redux')
const actions = require('../../actions')
const t = require('../../../i18n')
class NewAccountCreateForm extends Component {
constructor (props) {
@ -13,7 +14,7 @@ class NewAccountCreateForm extends Component {
this.state = {
newAccountName: '',
defaultAccountName: `Account ${newAccountNumber}`,
defaultAccountName: t('newAccountNumberName', [newAccountNumber]),
}
}
@ -24,7 +25,7 @@ class NewAccountCreateForm extends Component {
return h('div.new-account-create-form', [
h('div.new-account-create-form__input-label', {}, [
'Account Name',
t('accountName'),
]),
h('div.new-account-create-form__input-wrapper', {}, [
@ -37,16 +38,16 @@ class NewAccountCreateForm extends Component {
h('div.new-account-create-form__buttons', {}, [
h('button.new-account-create-form__button-cancel', {
h('button.new-account-create-form__button-cancel.allcaps', {
onClick: () => this.props.goHome(),
}, [
'CANCEL',
t('cancel'),
]),
h('button.new-account-create-form__button-create', {
h('button.new-account-create-form__button-create.allcaps', {
onClick: () => this.props.createAccount(newAccountName || defaultAccountName),
}, [
'CREATE',
t('create'),
]),
]),

@ -3,6 +3,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const t = require('../../../i18n')
const { getCurrentViewContext } = require('../../selectors')
const classnames = require('classnames')
@ -45,7 +46,7 @@ AccountDetailsModal.prototype.render = function () {
h('div.new-account__header', [
h('div.new-account__title', 'New Account'),
h('div.new-account__title', t('newAccount')),
h('div.new-account__tabs', [
@ -55,7 +56,7 @@ AccountDetailsModal.prototype.render = function () {
'new-account__tabs__unselected cursor-pointer': displayedForm !== 'CREATE',
}),
onClick: () => displayForm('CREATE'),
}, 'Create'),
}, t('createDen')),
h('div.new-account__tabs__tab', {
className: classnames('new-account__tabs__tab', {
@ -63,7 +64,7 @@ AccountDetailsModal.prototype.render = function () {
'new-account__tabs__unselected cursor-pointer': displayedForm !== 'IMPORT',
}),
onClick: () => displayForm('IMPORT'),
}, 'Import'),
}, t('import')),
]),

@ -75,6 +75,8 @@ var actions = {
resetAccount,
showNewVaultSeed: showNewVaultSeed,
showInfoPage: showInfoPage,
CLOSE_WELCOME_SCREEN: 'CLOSE_WELCOME_SCREEN',
closeWelcomeScreen,
// seed recovery actions
REVEAL_SEED_CONFIRMATION: 'REVEAL_SEED_CONFIRMATION',
revealSeedConfirmation: revealSeedConfirmation,
@ -788,7 +790,7 @@ function updateTransaction (txData) {
function updateAndApproveTx (txData) {
log.info('actions: updateAndApproveTx: ' + JSON.stringify(txData))
return (dispatch) => {
log.debug(`actions calling background.updateAndApproveTx`)
log.debug(`actions calling background.updateAndApproveTx.`)
background.updateAndApproveTransaction(txData, (err) => {
dispatch(actions.hideLoadingIndication())
dispatch(actions.updateTransactionParams(txData.id, txData.txParams))
@ -947,6 +949,12 @@ function showNewVaultSeed (seed) {
}
}
function closeWelcomeScreen () {
return {
type: actions.CLOSE_WELCOME_SCREEN,
}
}
function backToUnlockView () {
return {
type: actions.BACK_TO_UNLOCK_VIEW,

@ -4,6 +4,7 @@ const connect = require('react-redux').connect
const h = require('react-hyperscript')
const actions = require('./actions')
const classnames = require('classnames')
const t = require('../i18n')
// mascara
const MascaraFirstTime = require('../../mascara/src/app/first-time').default
@ -12,6 +13,8 @@ const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ethe
const OldUIInitializeMenuScreen = require('./first-time/init-menu')
const InitializeMenuScreen = MascaraFirstTime
const NewKeyChainScreen = require('./new-keychain')
const WelcomeScreen = require('./welcome-screen').default
// accounts
const MainContainer = require('./main-container')
const SendTransactionScreen2 = require('./components/send/send-v2-container')
@ -91,6 +94,7 @@ function mapStateToProps (state) {
betaUI: state.metamask.featureFlags.betaUI,
isRevealingSeedWords: state.metamask.isRevealingSeedWords,
Qr: state.appState.Qr,
welcomeScreenSeen: state.metamask.welcomeScreenSeen,
// state needed to get account dropdown temporarily rendering from app bar
identities,
@ -244,6 +248,7 @@ App.prototype.renderAppBar = function () {
isInitialized,
betaUI,
isPopup,
welcomeScreenSeen,
} = this.props
if (window.METAMASK_UI_TYPE === 'notification') {
@ -269,7 +274,7 @@ App.prototype.renderAppBar = function () {
style: {},
}, [
h('.app-header.flex-row.flex-space-between', {
(isInitialized || welcomeScreenSeen || isPopup || !betaUI) && h('.app-header.flex-row.flex-space-between', {
className: classnames({
'app-header--initialized': !isOnboarding,
}),
@ -289,12 +294,12 @@ App.prototype.renderAppBar = function () {
// metamask name
h('.flex-row', [
h('h1', 'MetaMask'),
h('div.beta-label', 'BETA'),
h('h1', t('appName')),
h('div.beta-label', t('beta')),
]),
]),
h('div.header__right-actions', [
betaUI && isInitialized && h('div.header__right-actions', [
h('div.network-component-wrapper', {
style: {},
}, [
@ -324,8 +329,12 @@ App.prototype.renderAppBar = function () {
]),
]),
!isInitialized && !isPopup && betaUI && h('h2.alpha-warning',
'Please be aware that this version is still under development'),
!isInitialized && !isPopup && betaUI && h('h2', {
className: classnames({
'alpha-warning': welcomeScreenSeen,
'alpha-warning-welcome-screen': !welcomeScreenSeen,
}),
}, 'Please be aware that this version is still under development'),
])
)
@ -369,11 +378,18 @@ App.prototype.renderPrimary = function () {
isOnboarding,
betaUI,
isRevealingSeedWords,
welcomeScreenSeen,
Qr,
isInitialized,
isUnlocked,
} = props
const isMascaraOnboarding = isMascara && isOnboarding
const isBetaUIOnboarding = betaUI && isOnboarding && !props.isPopup && !isRevealingSeedWords
if (!welcomeScreenSeen && betaUI && !isInitialized && !isUnlocked) {
return h(WelcomeScreen)
}
if (isMascaraOnboarding || isBetaUIOnboarding) {
return h(MascaraFirstTime)
}

@ -9,6 +9,7 @@ const DropdownMenuItem = require('./dropdown').DropdownMenuItem
const Identicon = require('./identicon')
const ethUtil = require('ethereumjs-util')
const copyToClipboard = require('copy-to-clipboard')
const t = require('../../i18n')
class AccountDropdowns extends Component {
constructor (props) {
@ -79,7 +80,7 @@ class AccountDropdowns extends Component {
try { // Sometimes keyrings aren't loaded yet:
const type = keyring.type
const isLoose = type !== 'HD Key Tree'
return isLoose ? h('.keyring-label', 'LOOSE') : null
return isLoose ? h('.keyring-label.allcaps', t('loose')) : null
} catch (e) { return }
}
@ -129,7 +130,7 @@ class AccountDropdowns extends Component {
diameter: 32,
},
),
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, 'Create Account'),
h('span', { style: { marginLeft: '20px', fontSize: '24px' } }, t('createAccount')),
],
),
h(
@ -154,7 +155,7 @@ class AccountDropdowns extends Component {
fontSize: '24px',
marginBottom: '5px',
},
}, 'Import Account'),
}, t('importAccount')),
]
),
]
@ -192,7 +193,7 @@ class AccountDropdowns extends Component {
global.platform.openWindow({ url })
},
},
'View account on Etherscan',
t('etherscanView'),
),
h(
DropdownMenuItem,
@ -204,7 +205,7 @@ class AccountDropdowns extends Component {
actions.showQrView(selected, identity ? identity.name : '')
},
},
'Show QR Code',
t('showQRCode'),
),
h(
DropdownMenuItem,
@ -216,7 +217,7 @@ class AccountDropdowns extends Component {
copyToClipboard(checkSumAddress)
},
},
'Copy Address to clipboard',
t('copyAddress'),
),
h(
DropdownMenuItem,
@ -226,7 +227,7 @@ class AccountDropdowns extends Component {
actions.requestAccountExport()
},
},
'Export Private Key',
t('exportPrivateKey'),
),
]
)

@ -6,6 +6,7 @@ const copyToClipboard = require('copy-to-clipboard')
const actions = require('../actions')
const ethUtil = require('ethereumjs-util')
const connect = require('react-redux').connect
const t = require('../../i18n')
module.exports = connect(mapStateToProps)(ExportAccountView)
@ -35,7 +36,7 @@ ExportAccountView.prototype.render = function () {
if (notExporting) return h('div')
if (exportRequested) {
const warning = `Export private keys at your own risk.`
const warning = t('exportPrivateKeyWarning')
return (
h('div', {
style: {
@ -53,7 +54,7 @@ ExportAccountView.prototype.render = function () {
h('p.error', warning),
h('input#exportAccount.sizing-input', {
type: 'password',
placeholder: 'confirm password',
placeholder: t('confirmPassword').toLowerCase(),
onKeyPress: this.onExportKeyPress.bind(this),
style: {
position: 'relative',
@ -74,10 +75,10 @@ ExportAccountView.prototype.render = function () {
style: {
marginRight: '10px',
},
}, 'Submit'),
}, t('submit')),
h('button', {
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
}, 'Cancel'),
}, t('cancel')),
]),
(this.props.warning) && (
h('span.error', {
@ -98,7 +99,7 @@ ExportAccountView.prototype.render = function () {
margin: '0 20px',
},
}, [
h('label', 'Your private key (click to copy):'),
h('label', t('copyPrivateKey') + ':'),
h('p.error.cursor-pointer', {
style: {
textOverflow: 'ellipsis',
@ -112,13 +113,13 @@ ExportAccountView.prototype.render = function () {
}, plainKey),
h('button', {
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
}, 'Done'),
}, t('done')),
h('button', {
style: {
marginLeft: '10px',
},
onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey),
}, 'Save as File'),
}, t('saveAsFile')),
])
}
}

@ -6,6 +6,7 @@ const actions = require('../../actions')
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
const Identicon = require('../identicon')
const { formatBalance } = require('../../util')
const t = require('../../../i18n')
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountMenu)
@ -70,10 +71,10 @@ AccountMenu.prototype.render = function () {
h(Item, {
className: 'account-menu__header',
}, [
'My Accounts',
t('myAccounts'),
h('button.account-menu__logout-button', {
onClick: lockMetamask,
}, 'Log out'),
}, t('logout')),
]),
h(Divider),
h('div.account-menu__accounts', this.renderAccounts()),
@ -81,23 +82,23 @@ AccountMenu.prototype.render = function () {
h(Item, {
onClick: () => showNewAccountPage('CREATE'),
icon: h('img.account-menu__item-icon', { src: 'images/plus-btn-white.svg' }),
text: 'Create Account',
text: t('createAccount'),
}),
h(Item, {
onClick: () => showNewAccountPage('IMPORT'),
icon: h('img.account-menu__item-icon', { src: 'images/import-account.svg' }),
text: 'Import Account',
text: t('importAccount'),
}),
h(Divider),
h(Item, {
onClick: showInfoPage,
icon: h('img.account-menu__item-icon', { src: 'images/mm-info-icon.svg' }),
text: 'Info & Help',
icon: h('img', { src: 'images/mm-info-icon.svg' }),
text: t('infoHelp'),
}),
h(Item, {
onClick: showConfigPage,
icon: h('img.account-menu__item-icon', { src: 'images/settings.svg' }),
text: 'Settings',
text: t('settings'),
}),
])
}
@ -155,6 +156,6 @@ AccountMenu.prototype.indicateIfLoose = function (keyring) {
try { // Sometimes keyrings aren't loaded yet:
const type = keyring.type
const isLoose = type !== 'HD Key Tree'
return isLoose ? h('.keyring-label', 'IMPORTED') : null
return isLoose ? h('.keyring-label.allcaps', t('imported')) : null
} catch (e) { return }
}

@ -4,6 +4,7 @@ const inherits = require('util').inherits
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const extend = require('xtend')
const t = require('../../i18n')
module.exports = BnAsDecimalInput
@ -136,13 +137,13 @@ BnAsDecimalInput.prototype.constructWarning = function () {
let message = name ? name + ' ' : ''
if (min && max) {
message += `must be greater than or equal to ${newMin} ${suffix} and less than or equal to ${newMax} ${suffix}.`
message += t('betweenMinAndMax', [`${newMin} ${suffix}`, `${newMax} ${suffix}`])
} else if (min) {
message += `must be greater than or equal to ${newMin} ${suffix}.`
message += t('greaterThanMin', [`${newMin} ${suffix}`])
} else if (max) {
message += `must be less than or equal to ${newMax} ${suffix}.`
message += t('lessThanMax', [`${newMax} ${suffix}`])
} else {
message += 'Invalid input.'
message += t('invalidInput')
}
return message

@ -9,6 +9,7 @@ const Loading = require('./loading')
const AccountPanel = require('./account-panel')
const RadioList = require('./custom-radio-list')
const networkNames = require('../../../app/scripts/config.js').networkNames
const t = require('../../i18n')
module.exports = connect(mapStateToProps)(BuyButtonSubview)
@ -76,7 +77,7 @@ BuyButtonSubview.prototype.headerSubview = function () {
paddingTop: '4px',
paddingBottom: '4px',
},
}, 'Deposit Eth'),
}, t('depositEth')),
]),
// loading indication
@ -118,7 +119,7 @@ BuyButtonSubview.prototype.headerSubview = function () {
paddingTop: '4px',
paddingBottom: '4px',
},
}, 'Select Service'),
}, t('selectService')),
]),
])
@ -143,7 +144,7 @@ BuyButtonSubview.prototype.primarySubview = function () {
case '4':
case '42':
const networkName = networkNames[network]
const label = `${networkName} Test Faucet`
const label = `${networkName} ${t('testFaucet')}`
return (
h('div.flex-column', {
style: {
@ -164,14 +165,14 @@ BuyButtonSubview.prototype.primarySubview = function () {
style: {
marginTop: '15px',
},
}, 'Borrow With Dharma (Beta)')
}, t('borrowDharma'))
) : null,
])
)
default:
return (
h('h2.error', 'Unknown network ID')
h('h2.error', t('unknownNetworkId'))
)
}
@ -203,8 +204,8 @@ BuyButtonSubview.prototype.mainnetSubview = function () {
'ShapeShift',
],
subtext: {
'Coinbase': 'Crypto/FIAT (USA only)',
'ShapeShift': 'Crypto',
'Coinbase': `${t('crypto')}/${t('fiat')} (${t('usaOnly')})`,
'ShapeShift': t('crypto'),
},
onClick: this.radioHandler.bind(this),
}),

@ -3,6 +3,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../actions')
const t = require('../../i18n')
module.exports = connect(mapStateToProps)(CoinbaseForm)
@ -37,11 +38,11 @@ CoinbaseForm.prototype.render = function () {
}, [
h('button.btn-green', {
onClick: this.toCoinbase.bind(this),
}, 'Continue to Coinbase'),
}, t('continueToCoinbase')),
h('button.btn-red', {
onClick: () => props.dispatch(actions.goHome()),
}, 'Cancel'),
}, t('cancel')),
]),
])
}

@ -2,6 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const copyToClipboard = require('copy-to-clipboard')
const t = require('../../i18n')
const Tooltip = require('./tooltip')
@ -22,7 +23,7 @@ CopyButton.prototype.render = function () {
const value = props.value
const copied = state.copied
const message = copied ? 'Copied' : props.title || ' Copy '
const message = copied ? t('copiedButton') : props.title || t('copyButton')
return h('.copy-button', {
style: {

@ -4,6 +4,7 @@ const inherits = require('util').inherits
const Tooltip = require('./tooltip')
const copyToClipboard = require('copy-to-clipboard')
const t = require('../../i18n')
module.exports = Copyable
@ -22,7 +23,7 @@ Copyable.prototype.render = function () {
const { copied } = state
return h(Tooltip, {
title: copied ? 'Copied!' : 'Copy',
title: copied ? t('copiedExclamation') : t('copy'),
position: 'bottom',
}, h('span', {
style: {

@ -3,6 +3,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const t = require('../../../i18n')
const GasModalCard = require('./gas-modal-card')
const ethUtil = require('ethereumjs-util')
@ -146,7 +147,7 @@ CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) {
})
if (!balanceIsSufficient) {
error = 'Insufficient balance for current gas total'
error = t('balanceIsInsufficientGas')
}
const gasLimitTooLow = gasLimit && conversionGreaterThan(
@ -162,7 +163,7 @@ CustomizeGasModal.prototype.validate = function ({ gasTotal, gasLimit }) {
)
if (gasLimitTooLow) {
error = 'Gas limit must be at least 21000'
error = t('gasLimitTooLow')
}
this.setState({ error })
@ -239,7 +240,7 @@ CustomizeGasModal.prototype.render = function () {
}, [
h('div.send-v2__customize-gas__header', {}, [
h('div.send-v2__customize-gas__title', 'Customize Gas'),
h('div.send-v2__customize-gas__title', t('customGas')),
h('div.send-v2__customize-gas__close', {
onClick: hideModal,
@ -255,8 +256,8 @@ CustomizeGasModal.prototype.render = function () {
// max: 1000,
step: multiplyCurrencies(MIN_GAS_PRICE_GWEI, 10),
onChange: value => this.convertAndSetGasPrice(value),
title: 'Gas Price (GWEI)',
copy: 'We calculate the suggested gas prices based on network success rates.',
title: t('gasPrice'),
copy: t('gasPriceCalculation'),
}),
h(GasModalCard, {
@ -265,8 +266,8 @@ CustomizeGasModal.prototype.render = function () {
// max: 100000,
step: 1,
onChange: value => this.convertAndSetGasLimit(value),
title: 'Gas Limit',
copy: 'We calculate the suggested gas limit based on network success rates.',
title: t('gasLimit'),
copy: t('gasLimitCalculation'),
}),
]),
@ -279,16 +280,16 @@ CustomizeGasModal.prototype.render = function () {
h('div.send-v2__customize-gas__revert', {
onClick: () => this.revert(),
}, ['Revert']),
}, [t('revert')]),
h('div.send-v2__customize-gas__buttons', [
h('div.send-v2__customize-gas__cancel', {
h('div.send-v2__customize-gas__cancel.allcaps', {
onClick: this.props.hideModal,
}, ['CANCEL']),
}, [t('cancel')]),
h(`div.send-v2__customize-gas__save${error ? '__error' : ''}`, {
h(`div.send-v2__customize-gas__save${error ? '__error' : ''}.allcaps`, {
onClick: () => !error && this.save(gasPrice, gasLimit, gasTotal),
}, ['SAVE']),
}, [t('save')]),
]),
]),

@ -10,6 +10,7 @@ const Identicon = require('../../identicon')
const ethUtil = require('ethereumjs-util')
const copyToClipboard = require('copy-to-clipboard')
const { formatBalance } = require('../../../util')
const t = require('../../../../i18n')
class AccountDropdowns extends Component {
constructor (props) {
@ -121,7 +122,7 @@ class AccountDropdowns extends Component {
flex: '3 3 auto',
},
}, [
h('span.account-dropdown-edit-button', {
h('span.account-dropdown-edit-button.allcaps', {
style: {
fontSize: '16px',
},
@ -129,7 +130,7 @@ class AccountDropdowns extends Component {
actions.showEditAccountModal(identity)
},
}, [
'Edit',
t('edit'),
]),
]),
@ -143,7 +144,7 @@ class AccountDropdowns extends Component {
try { // Sometimes keyrings aren't loaded yet:
const type = keyring.type
const isLoose = type !== 'HD Key Tree'
return isLoose ? h('.keyring-label', 'IMPORTED') : null
return isLoose ? h('.keyring-label.allcaps', t('loose')) : null
} catch (e) { return }
}
@ -201,7 +202,7 @@ class AccountDropdowns extends Component {
fontSize: '16px',
lineHeight: '23px',
},
}, 'Create Account'),
}, t('createAccount')),
],
),
h(
@ -235,7 +236,7 @@ class AccountDropdowns extends Component {
fontSize: '16px',
lineHeight: '23px',
},
}, 'Import Account'),
}, t('importAccount')),
]
),
]
@ -286,7 +287,7 @@ class AccountDropdowns extends Component {
menuItemStyles,
),
},
'Account Details',
t('accountDetails'),
),
h(
DropdownMenuItem,
@ -302,7 +303,7 @@ class AccountDropdowns extends Component {
menuItemStyles,
),
},
'View account on Etherscan',
t('etherscanView'),
),
h(
DropdownMenuItem,
@ -318,7 +319,7 @@ class AccountDropdowns extends Component {
menuItemStyles,
),
},
'Copy Address to clipboard',
t('copyAddress'),
),
h(
DropdownMenuItem,
@ -330,7 +331,7 @@ class AccountDropdowns extends Component {
menuItemStyles,
),
},
'Export Private Key',
t('exportPrivateKey'),
),
h(
DropdownMenuItem,
@ -345,7 +346,7 @@ class AccountDropdowns extends Component {
menuItemStyles,
),
},
'Add Token',
t('addToken'),
),
]
@ -463,4 +464,3 @@ function mapStateToProps (state) {
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDropdowns)

@ -6,6 +6,7 @@ const actions = require('../../actions')
const Dropdown = require('./components/dropdown').Dropdown
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkDropdownIcon = require('./components/network-dropdown-icon')
const t = require('../../../i18n')
const R = require('ramda')
// classes from nodes of the toggle element.
@ -93,13 +94,13 @@ NetworkDropdown.prototype.render = function () {
}, [
h('div.network-dropdown-header', {}, [
h('div.network-dropdown-title', {}, 'Networks'),
h('div.network-dropdown-title', {}, t('networks')),
h('div.network-dropdown-divider'),
h('div.network-dropdown-content',
{},
'The default network for Ether transactions is Main Net.'
t('defaultNetwork')
),
]),
@ -121,7 +122,7 @@ NetworkDropdown.prototype.render = function () {
style: {
color: providerType === 'mainnet' ? '#ffffff' : '#9b9b9b',
},
}, 'Main Ethereum Network'),
}, t('mainnet')),
]
),
@ -143,7 +144,7 @@ NetworkDropdown.prototype.render = function () {
style: {
color: providerType === 'ropsten' ? '#ffffff' : '#9b9b9b',
},
}, 'Ropsten Test Network'),
}, t('ropsten')),
]
),
@ -165,7 +166,7 @@ NetworkDropdown.prototype.render = function () {
style: {
color: providerType === 'kovan' ? '#ffffff' : '#9b9b9b',
},
}, 'Kovan Test Network'),
}, t('kovan')),
]
),
@ -187,7 +188,7 @@ NetworkDropdown.prototype.render = function () {
style: {
color: providerType === 'rinkeby' ? '#ffffff' : '#9b9b9b',
},
}, 'Rinkeby Test Network'),
}, t('rinkeby')),
]
),
@ -209,7 +210,7 @@ NetworkDropdown.prototype.render = function () {
style: {
color: activeNetwork === 'http://localhost:8545' ? '#ffffff' : '#9b9b9b',
},
}, 'Localhost 8545'),
}, t('localhost')),
]
),
@ -233,7 +234,7 @@ NetworkDropdown.prototype.render = function () {
style: {
color: activeNetwork === 'custom' ? '#ffffff' : '#9b9b9b',
},
}, 'Custom RPC'),
}, t('customRPC')),
]
),
@ -248,15 +249,15 @@ NetworkDropdown.prototype.getNetworkName = function () {
let name
if (providerName === 'mainnet') {
name = 'Main Ethereum Network'
name = t('mainnet')
} else if (providerName === 'ropsten') {
name = 'Ropsten Test Network'
name = t('ropsten')
} else if (providerName === 'kovan') {
name = 'Kovan Test Network'
name = t('kovan')
} else if (providerName === 'rinkeby') {
name = 'Rinkeby Test Network'
name = t('rinkeby')
} else {
name = 'Unknown Private Network'
name = t('unknownNetwork')
}
return name

@ -3,6 +3,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const t = require('../../../i18n')
module.exports = connect(null, mapDispatchToProps)(TokenMenuDropdown)
@ -43,7 +44,7 @@ TokenMenuDropdown.prototype.render = function () {
showHideTokenConfirmationModal(this.props.token)
this.props.onClose()
},
}, 'Hide Token'),
}, t('hideToken')),
]),
]),

@ -8,6 +8,7 @@ const ENS = require('ethjs-ens')
const networkMap = require('ethjs-ens/lib/network-map.json')
const ensRE = /.+\..+$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const t = require('../../i18n')
module.exports = EnsInput
@ -89,13 +90,13 @@ EnsInput.prototype.lookupEnsName = function () {
log.info(`ENS attempting to resolve name: ${recipient}`)
this.ens.lookup(recipient.trim())
.then((address) => {
if (address === ZERO_ADDRESS) throw new Error('No address has been set for this name.')
if (address === ZERO_ADDRESS) throw new Error(t('noAddressForName'))
if (address !== ensResolution) {
this.setState({
loadingEns: false,
ensResolution: address,
nickname: recipient.trim(),
hoverText: address + '\nClick to Copy',
hoverText: address + '\n' + t('clickCopy'),
ensFailure: false,
})
}

@ -4,6 +4,7 @@ const inherits = require('util').inherits
const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const extend = require('xtend')
const t = require('../../i18n')
module.exports = HexAsDecimalInput
@ -126,13 +127,13 @@ HexAsDecimalInput.prototype.constructWarning = function () {
let message = name ? name + ' ' : ''
if (min && max) {
message += `must be greater than or equal to ${min} and less than or equal to ${max}.`
message += t('betweenMinAndMax', [min, max])
} else if (min) {
message += `must be greater than or equal to ${min}.`
message += t('greaterThanMin', [min])
} else if (max) {
message += `must be less than or equal to ${max}.`
message += t('lessThanMax', [max])
} else {
message += 'Invalid input.'
message += t('invalidInput')
}
return message

@ -8,6 +8,7 @@ const { getSelectedIdentity } = require('../../selectors')
const genAccountLink = require('../../../lib/account-link.js')
const QrView = require('../qr-code')
const EditableLabel = require('../editable-label')
const t = require('../../../i18n')
function mapStateToProps (state) {
return {
@ -64,12 +65,12 @@ AccountDetailsModal.prototype.render = function () {
h('button.btn-clear.account-modal__button', {
onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
}, 'View account on Etherscan'),
}, t('etherscanView')),
// Holding on redesign for Export Private Key functionality
h('button.btn-clear.account-modal__button', {
onClick: () => showExportPrivateKeyModal(),
}, 'Export private key'),
}, t('exportPrivateKey')),
])
}

@ -5,6 +5,7 @@ const connect = require('react-redux').connect
const actions = require('../../actions')
const { getSelectedIdentity } = require('../../selectors')
const Identicon = require('../identicon')
const t = require('../../../i18n')
function mapStateToProps (state) {
return {
@ -59,7 +60,7 @@ AccountModalContainer.prototype.render = function () {
h('i.fa.fa-angle-left.fa-lg'),
h('span.account-modal-back__text', ' Back'),
h('span.account-modal-back__text', ' ' + t('back')),
]),

@ -4,6 +4,7 @@ const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const networkNames = require('../../../../app/scripts/config.js').networkNames
const t = require('../../../i18n')
function mapStateToProps (state) {
return {
@ -56,15 +57,15 @@ BuyOptions.prototype.render = function () {
}, [
h('div.buy-modal-content-title', {
style: {},
}, 'Transfers'),
h('div', {}, 'How would you like to deposit Ether?'),
}, t('transfers')),
h('div', {}, t('howToDeposit')),
]),
h('div.buy-modal-content-options.flex-column.flex-center', {}, [
isTestNetwork
? this.renderModalContentOption(networkName, 'Test Faucet', () => toFaucet(network))
: this.renderModalContentOption('Coinbase', 'Deposit with Fiat', () => toCoinbase(address)),
? this.renderModalContentOption(networkName, t('testFaucet'), () => toFaucet(network))
: this.renderModalContentOption('Coinbase', t('depositFiat'), () => toCoinbase(address)),
// h('div.buy-modal-content-option', {}, [
// h('div.buy-modal-content-option-title', {}, 'Shapeshift'),
@ -72,8 +73,8 @@ BuyOptions.prototype.render = function () {
// ]),,
this.renderModalContentOption(
'Direct Deposit',
'Deposit from another account',
t('directDeposit'),
t('depositFromAccount'),
() => this.goToAccountDetailsModal()
),
@ -84,7 +85,7 @@ BuyOptions.prototype.render = function () {
background: 'white',
},
onClick: () => { this.props.hideModal() },
}, h('div.buy-modal-content-footer#buy-modal-content-footer-text', {}, 'Cancel')),
}, h('div.buy-modal-content-footer#buy-modal-content-footer-text', {}, t('cancel'))),
]),
])
}

@ -5,18 +5,18 @@ const connect = require('react-redux').connect
const actions = require('../../actions')
const networkNames = require('../../../../app/scripts/config.js').networkNames
const ShapeshiftForm = require('../shapeshift-form')
const DIRECT_DEPOSIT_ROW_TITLE = 'Directly Deposit Ether'
const DIRECT_DEPOSIT_ROW_TEXT = `If you already have some Ether, the quickest way to get Ether in
your new wallet by direct deposit.`
const COINBASE_ROW_TITLE = 'Buy on Coinbase'
const COINBASE_ROW_TEXT = `Coinbase is the world’s most popular way to buy and sell bitcoin,
ethereum, and litecoin.`
const SHAPESHIFT_ROW_TITLE = 'Deposit with ShapeShift'
const SHAPESHIFT_ROW_TEXT = `If you own other cryptocurrencies, you can trade and deposit Ether
directly into your MetaMask wallet. No Account Needed.`
const FAUCET_ROW_TITLE = 'Test Faucet'
const facuetRowText = networkName => `Get Ether from a faucet for the ${networkName}`
const t = require('../../../i18n')
const DIRECT_DEPOSIT_ROW_TITLE = t('directDepositEther')
const DIRECT_DEPOSIT_ROW_TEXT = t('directDepositEtherExplainer')
const COINBASE_ROW_TITLE = t('buyCoinbase')
const COINBASE_ROW_TEXT = t('buyCoinbaseExplainer')
const SHAPESHIFT_ROW_TITLE = t('depositShapeShift')
const SHAPESHIFT_ROW_TEXT = t('depositShapeShiftExplainer')
const FAUCET_ROW_TITLE = t('testFaucet')
const facuetRowText = (networkName) => {
return t('getEtherFromFaucet', [networkName])
}
function mapStateToProps (state) {
return {
@ -83,7 +83,7 @@ DepositEtherModal.prototype.renderRow = function ({
]),
h('div.deposit-ether-modal__buy-row__logo', [logo]),
h('div.deposit-ether-modal__buy-row__logo-container', [logo]),
h('div.deposit-ether-modal__buy-row__description', [
@ -109,17 +109,17 @@ DepositEtherModal.prototype.render = function () {
const isTestNetwork = ['3', '4', '42'].find(n => n === network)
const networkName = networkNames[network]
return h('div.deposit-ether-modal', {}, [
return h('div.page-container.page-container--full-width', {}, [
h('div.deposit-ether-modal__header', [
h('div.page-container__header', [
h('div.deposit-ether-modal__header__title', ['Deposit Ether']),
h('div.page-container__title', [t('depositEther')]),
h('div.deposit-ether-modal__header__description', [
'To interact with decentralized applications using MetaMask, you’ll need Ether in your wallet.',
h('div.page-container__subtitle', [
t('needEtherInWallet'),
]),
h('div.deposit-ether-modal__header__close', {
h('div.page-container__header-close', {
onClick: () => {
this.setState({ buyingWithShapeshift: false })
this.props.hideWarning()
@ -129,13 +129,15 @@ DepositEtherModal.prototype.render = function () {
]),
h('.page-container__content', {}, [
h('div.deposit-ether-modal__buy-rows', [
this.renderRow({
logo: h('img.deposit-ether-modal__buy-row__eth-logo', { src: '../../../images/eth_logo.svg' }),
title: DIRECT_DEPOSIT_ROW_TITLE,
text: DIRECT_DEPOSIT_ROW_TEXT,
buttonLabel: 'View Account',
buttonLabel: t('viewAccount'),
onButtonClick: () => this.goToAccountDetailsModal(),
hide: buyingWithShapeshift,
}),
@ -144,29 +146,34 @@ DepositEtherModal.prototype.render = function () {
logo: h('i.fa.fa-tint.fa-2x'),
title: FAUCET_ROW_TITLE,
text: facuetRowText(networkName),
buttonLabel: 'Get Ether',
buttonLabel: t('getEther'),
onButtonClick: () => toFaucet(network),
hide: !isTestNetwork || buyingWithShapeshift,
}),
this.renderRow({
logo: h('img.deposit-ether-modal__buy-row__coinbase-logo', {
src: '../../../images/coinbase logo.png',
logo: h('div.deposit-ether-modal__logo', {
style: {
backgroundImage: 'url(\'../../../images/coinbase logo.png\')',
height: '40px',
},
}),
title: COINBASE_ROW_TITLE,
text: COINBASE_ROW_TEXT,
buttonLabel: 'Continue to Coinbase',
buttonLabel: t('continueToCoinbase'),
onButtonClick: () => toCoinbase(address),
hide: isTestNetwork || buyingWithShapeshift,
}),
this.renderRow({
logo: h('img.deposit-ether-modal__buy-row__shapeshift-logo', {
src: '../../../images/shapeshift logo.png',
logo: h('div.deposit-ether-modal__logo', {
style: {
backgroundImage: 'url(\'../../../images/shapeshift logo.png\')',
},
}),
title: SHAPESHIFT_ROW_TITLE,
text: SHAPESHIFT_ROW_TEXT,
buttonLabel: 'Buy with Shapeshift',
buttonLabel: t('shapeshiftBuy'),
onButtonClick: () => this.setState({ buyingWithShapeshift: true }),
hide: isTestNetwork,
hideButton: buyingWithShapeshift,
@ -179,6 +186,8 @@ DepositEtherModal.prototype.render = function () {
buyingWithShapeshift && h(ShapeshiftForm),
]),
]),
])
}

@ -4,6 +4,7 @@ const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const { getSelectedAccount } = require('../../selectors')
const t = require('../../../i18n')
function mapStateToProps (state) {
return {
@ -50,7 +51,7 @@ EditAccountNameModal.prototype.render = function () {
]),
h('div.edit-account-name-modal-title', {
}, ['Edit Account Name']),
}, [t('editAccountName')]),
h('input.edit-account-name-modal-input', {
placeholder: identity.name,
@ -60,7 +61,7 @@ EditAccountNameModal.prototype.render = function () {
value: this.state.inputText,
}, []),
h('button.btn-clear.edit-account-name-modal-save-button', {
h('button.btn-clear.edit-account-name-modal-save-button.allcaps', {
onClick: () => {
if (this.state.inputText.length !== 0) {
saveAccountLabel(identity.address, this.state.inputText)
@ -69,7 +70,7 @@ EditAccountNameModal.prototype.render = function () {
},
disabled: this.state.inputText.length === 0,
}, [
'SAVE',
t('save'),
]),
]),

@ -7,6 +7,7 @@ const actions = require('../../actions')
const AccountModalContainer = require('./account-modal-container')
const { getSelectedIdentity } = require('../../selectors')
const ReadOnlyInput = require('../readonly-input')
const t = require('../../../i18n')
const copyToClipboard = require('copy-to-clipboard')
function mapStateToProps (state) {
@ -48,8 +49,8 @@ ExportPrivateKeyModal.prototype.exportAccountAndGetPrivateKey = function (passwo
ExportPrivateKeyModal.prototype.renderPasswordLabel = function (privateKey) {
return h('span.private-key-password-label', privateKey
? 'This is your private key (click to copy)'
: 'Type Your Password'
? t('copyPrivateKey')
: t('typePassword')
)
}
@ -86,8 +87,8 @@ ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password,
),
(privateKey
? this.renderButton('btn-clear export-private-key__button', () => hideModal(), 'Done')
: this.renderButton('btn-clear export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), 'Confirm')
? this.renderButton('btn-clear export-private-key__button', () => hideModal(), t('done'))
: this.renderButton('btn-clear export-private-key__button', () => this.exportAccountAndGetPrivateKey(this.state.password, address), t('confirm'))
),
])
@ -120,7 +121,7 @@ ExportPrivateKeyModal.prototype.render = function () {
h('div.account-modal-divider'),
h('span.modal-body-title', 'Show Private Keys'),
h('span.modal-body-title', t('showPrivateKeys')),
h('div.private-key-password', {}, [
this.renderPasswordLabel(privateKey),
@ -130,10 +131,7 @@ ExportPrivateKeyModal.prototype.render = function () {
!warning ? null : h('span.private-key-password-error', warning),
]),
h('div.private-key-password-warning', `Warning: Never disclose this key.
Anyone with your private keys can take steal any assets held in your
account.`
),
h('div.private-key-password-warning', t('privateKeyWarning')),
this.renderButtons(privateKey, this.state.password, address, hideModal),

@ -4,6 +4,7 @@ const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../actions')
const Identicon = require('../identicon')
const t = require('../../../i18n')
function mapStateToProps (state) {
return {
@ -41,7 +42,7 @@ HideTokenConfirmationModal.prototype.render = function () {
h('div.hide-token-confirmation__container', {
}, [
h('div.hide-token-confirmation__title', {}, [
'Hide Token?',
t('hideTokenPrompt'),
]),
h(Identicon, {
@ -54,19 +55,19 @@ HideTokenConfirmationModal.prototype.render = function () {
h('div.hide-token-confirmation__symbol', {}, symbol),
h('div.hide-token-confirmation__copy', {}, [
'You can add this token back in the future by going go to “Add token” in your accounts options menu.',
t('readdToken'),
]),
h('div.hide-token-confirmation__buttons', {}, [
h('button.btn-cancel.hide-token-confirmation__button', {
h('button.btn-cancel.hide-token-confirmation__button.allcaps', {
onClick: () => hideModal(),
}, [
'CANCEL',
t('cancel'),
]),
h('button.btn-clear.hide-token-confirmation__button', {
h('button.btn-clear.hide-token-confirmation__button.allcaps', {
onClick: () => hideToken(address),
}, [
'HIDE',
t('hide'),
]),
]),
]),

@ -6,6 +6,7 @@ const FadeModal = require('boron').FadeModal
const actions = require('../../actions')
const isMobileView = require('../../../lib/is-mobile-view')
const isPopupOrNotification = require('../../../../app/scripts/lib/is-popup-or-notification')
const t = require('../../../i18n')
// Modal Components
const BuyOptions = require('./buy-options-modal')
@ -92,18 +93,20 @@ const MODALS = {
display: 'flex',
},
laptopModalStyle: {
width: '900px',
maxWidth: '900px',
width: '850px',
top: 'calc(10% + 10px)',
left: '0',
right: '0',
margin: '0 auto',
boxShadow: '0 0 6px 0 rgba(0,0,0,0.3)',
borderRadius: '8px',
borderRadius: '7px',
transform: 'none',
height: 'calc(80% - 20px)',
overflowY: 'hidden',
},
contentStyle: {
borderRadius: '8px',
borderRadius: '7px',
height: '100%',
},
},
@ -171,9 +174,8 @@ const MODALS = {
BETA_UI_NOTIFICATION_MODAL: {
contents: [
h(NotifcationModal, {
header: 'Welcome to the New UI (Beta)',
message: `You are now using the new Metamask UI. Take a look around, try out new features like sending tokens,
and let us know if you have any issues.`,
header: t('uiWelcome'),
message: t('uiWelcomeMessage'),
}),
],
mobileModalStyle: {
@ -189,9 +191,8 @@ const MODALS = {
OLD_UI_NOTIFICATION_MODAL: {
contents: [
h(NotifcationModal, {
header: 'Old UI',
message: `You have returned to the old UI. You can switch back to the New UI through the option in the top
right dropdown menu.`,
header: t('oldUI'),
message: t('oldUIMessage'),
}),
],
mobileModalStyle: {

@ -3,6 +3,7 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const { connect } = require('react-redux')
const actions = require('../../actions')
const t = require('../../../i18n')
class NewAccountModal extends Component {
constructor (props) {
@ -11,7 +12,7 @@ class NewAccountModal extends Component {
const newAccountNumber = numberOfExistingAccounts + 1
this.state = {
newAccountName: `Account ${newAccountNumber}`,
newAccountName: `${t('account')} ${newAccountNumber}`,
}
}
@ -22,7 +23,7 @@ class NewAccountModal extends Component {
h('div.new-account-modal-wrapper', {
}, [
h('div.new-account-modal-header', {}, [
'New Account',
t('newAccount'),
]),
h('div.modal-close-x', {
@ -30,19 +31,19 @@ class NewAccountModal extends Component {
}),
h('div.new-account-modal-content', {}, [
'Account Name',
t('accountName'),
]),
h('div.new-account-input-wrapper', {}, [
h('input.new-account-input', {
value: this.state.newAccountName,
placeholder: 'E.g. My new account',
placeholder: t('sampleAccountName'),
onChange: event => this.setState({ newAccountName: event.target.value }),
}, []),
]),
h('div.new-account-modal-content.after-input', {}, [
'or',
t('or'),
]),
h('div.new-account-modal-content.after-input.pointer', {
@ -50,13 +51,13 @@ class NewAccountModal extends Component {
this.props.hideModal()
this.props.showImportPage()
},
}, 'Import an account'),
}, t('importAnAccount')),
h('div.new-account-modal-content.button', {}, [
h('div.new-account-modal-content.button.allcaps', {}, [
h('button.btn-clear', {
onClick: () => this.props.createAccount(newAccountName),
}, [
'SAVE',
t('save'),
]),
]),
]),

@ -3,6 +3,7 @@ const h = require('react-hyperscript')
const classnames = require('classnames')
const inherits = require('util').inherits
const NetworkDropdownIcon = require('./dropdowns/components/network-dropdown-icon')
const t = require('../../i18n')
module.exports = Network
@ -33,7 +34,7 @@ Network.prototype.render = function () {
onClick: (event) => this.props.onClick(event),
}, [
h('img', {
title: 'Attempting to connect to blockchain.',
title: t('attemptingConnect'),
style: {
width: '27px',
},
@ -41,22 +42,22 @@ Network.prototype.render = function () {
}),
])
} else if (providerName === 'mainnet') {
hoverText = 'Main Ethereum Network'
hoverText = t('mainnet')
iconName = 'ethereum-network'
} else if (providerName === 'ropsten') {
hoverText = 'Ropsten Test Network'
hoverText = t('ropsten')
iconName = 'ropsten-test-network'
} else if (parseInt(networkNumber) === 3) {
hoverText = 'Ropsten Test Network'
hoverText = t('ropsten')
iconName = 'ropsten-test-network'
} else if (providerName === 'kovan') {
hoverText = 'Kovan Test Network'
hoverText = t('kovan')
iconName = 'kovan-test-network'
} else if (providerName === 'rinkeby') {
hoverText = 'Rinkeby Test Network'
hoverText = t('rinkeby')
iconName = 'rinkeby-test-network'
} else {
hoverText = 'Unknown Private Network'
hoverText = t('unknownNetwork')
iconName = 'unknown-private-network'
}
@ -84,7 +85,7 @@ Network.prototype.render = function () {
backgroundColor: '#038789', // $blue-lagoon
nonSelectBackgroundColor: '#15afb2',
}),
h('.network-name', 'Main Network'),
h('.network-name', t('mainnet')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
case 'ropsten-test-network':
@ -93,7 +94,7 @@ Network.prototype.render = function () {
backgroundColor: '#e91550', // $crimson
nonSelectBackgroundColor: '#ec2c50',
}),
h('.network-name', 'Ropsten Test Net'),
h('.network-name', t('ropsten')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
case 'kovan-test-network':
@ -102,7 +103,7 @@ Network.prototype.render = function () {
backgroundColor: '#690496', // $purple
nonSelectBackgroundColor: '#b039f3',
}),
h('.network-name', 'Kovan Test Net'),
h('.network-name', t('kovan')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
case 'rinkeby-test-network':
@ -111,7 +112,7 @@ Network.prototype.render = function () {
backgroundColor: '#ebb33f', // $tulip-tree
nonSelectBackgroundColor: '#ecb23e',
}),
h('.network-name', 'Rinkeby Test Net'),
h('.network-name', t('rinkeby')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
default:
@ -123,7 +124,7 @@ Network.prototype.render = function () {
},
}),
h('.network-name', 'Private Network'),
h('.network-name', t('privateNetwork')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
}

@ -4,6 +4,7 @@ const h = require('react-hyperscript')
const ReactMarkdown = require('react-markdown')
const linker = require('extension-link-enabler')
const findDOMNode = require('react-dom').findDOMNode
const t = require('../../i18n')
module.exports = Notice
@ -110,7 +111,7 @@ Notice.prototype.render = function () {
style: {
marginTop: '18px',
},
}, 'Accept'),
}, t('accept')),
])
)
}

@ -1,6 +1,7 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const t = require('../../i18n')
const AccountPanel = require('./account-panel')
@ -39,7 +40,7 @@ PendingMsgDetails.prototype.render = function () {
// message data
h('.tx-data.flex-column.flex-justify-center.flex-grow.select-none', [
h('.flex-column.flex-space-between', [
h('label.font-small', 'MESSAGE'),
h('label.font-small.allcaps', t('message')),
h('span.font-small', msgParams.data),
]),
]),
@ -47,4 +48,3 @@ PendingMsgDetails.prototype.render = function () {
])
)
}

@ -2,6 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const PendingTxDetails = require('./pending-msg-details')
const t = require('../../i18n')
module.exports = PendingMsg
@ -29,17 +30,14 @@ PendingMsg.prototype.render = function () {
fontWeight: 'bold',
textAlign: 'center',
},
}, 'Sign Message'),
}, t('signMessage')),
h('.error', {
style: {
margin: '10px',
},
}, [
`Signing this message can have
dangerous side effects. Only sign messages from
sites you fully trust with your entire account.
This dangerous method will be removed in a future version. `,
t('signNotice'),
h('a', {
href: 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527',
style: { color: 'rgb(247, 134, 28)' },
@ -48,7 +46,7 @@ PendingMsg.prototype.render = function () {
const url = 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527'
global.platform.openWindow({ url })
},
}, 'Read more here.'),
}, t('readMore')),
]),
// message details
@ -58,13 +56,12 @@ PendingMsg.prototype.render = function () {
h('.flex-row.flex-space-around', [
h('button', {
onClick: state.cancelMessage,
}, 'Cancel'),
}, t('cancel')),
h('button', {
onClick: state.signMessage,
}, 'Sign'),
}, t('sign')),
]),
])
)
}

@ -1,6 +1,7 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const t = require('../../i18n')
const AccountPanel = require('./account-panel')
const BinaryRenderer = require('./binary-renderer')
@ -45,7 +46,7 @@ PendingMsgDetails.prototype.render = function () {
height: '260px',
},
}, [
h('label.font-small', { style: { display: 'block' } }, 'MESSAGE'),
h('label.font-small.allcaps', { style: { display: 'block' } }, t('message')),
h(BinaryRenderer, {
value: data,
style: {
@ -57,4 +58,3 @@ PendingMsgDetails.prototype.render = function () {
])
)
}

@ -9,6 +9,7 @@ const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const hexToBn = require('../../../../app/scripts/lib/hex-to-bn')
const { conversionUtil } = require('../../conversion-util')
const t = require('../../../i18n')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
@ -55,7 +56,7 @@ ConfirmDeployContract.prototype.onSubmit = function (event) {
if (valid && this.verifyGasParams()) {
this.props.sendTransaction(txMeta, event)
} else {
this.props.dispatch(actions.displayWarning('Invalid Gas Parameters'))
this.props.dispatch(actions.displayWarning(t('invalidGasParams')))
this.setState({ submitting: false })
}
}
@ -200,7 +201,7 @@ ConfirmDeployContract.prototype.renderGasFee = function () {
return (
h('section.flex-row.flex-center.confirm-screen-row', [
h('span.confirm-screen-label.confirm-screen-section-column', [ 'Gas Fee' ]),
h('span.confirm-screen-label.confirm-screen-section-column', [ t('gasFee') ]),
h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', `${fiatGas} ${currentCurrency.toUpperCase()}`),
@ -239,8 +240,8 @@ ConfirmDeployContract.prototype.renderTotalPlusGas = function () {
return (
h('section.flex-row.flex-center.confirm-screen-total-box ', [
h('div.confirm-screen-section-column', [
h('span.confirm-screen-label', [ 'Total ' ]),
h('div.confirm-screen-total-box__subtitle', [ 'Amount + Gas' ]),
h('span.confirm-screen-label', [ t('total') + ' ' ]),
h('div.confirm-screen-total-box__subtitle', [ t('amountPlusGas') ]),
]),
h('div.confirm-screen-section-column', [
@ -271,10 +272,10 @@ ConfirmDeployContract.prototype.render = function () {
// Main Send token Card
h('div.confirm-screen-wrapper.flex-column.flex-grow', [
h('h3.flex-center.confirm-screen-header', [
h('button.confirm-screen-back-button', {
h('button.confirm-screen-back-button.allcaps', {
onClick: () => backToAccountDetail(selectedAddress),
}, 'BACK'),
h('div.confirm-screen-title', 'Confirm Contract'),
}, t('back')),
h('div.confirm-screen-title', t('confirmContract')),
h('div.confirm-screen-header-tip'),
]),
h('div.flex-row.flex-center.confirm-screen-identicons', [
@ -292,7 +293,7 @@ ConfirmDeployContract.prototype.render = function () {
h('i.fa.fa-arrow-right.fa-lg'),
h('div.confirm-screen-account-wrapper', [
h('i.fa.fa-file-text-o'),
h('span.confirm-screen-account-name', 'New Contract'),
h('span.confirm-screen-account-name', t('newContract')),
h('span.confirm-screen-account-number', ' '),
]),
]),
@ -310,7 +311,7 @@ ConfirmDeployContract.prototype.render = function () {
h('div.confirm-screen-rows', [
h('section.flex-row.flex-center.confirm-screen-row', [
h('span.confirm-screen-label.confirm-screen-section-column', [ 'From' ]),
h('span.confirm-screen-label.confirm-screen-section-column', [ t('from') ]),
h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', fromName),
h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`),
@ -318,9 +319,9 @@ ConfirmDeployContract.prototype.render = function () {
]),
h('section.flex-row.flex-center.confirm-screen-row', [
h('span.confirm-screen-label.confirm-screen-section-column', [ 'To' ]),
h('span.confirm-screen-label.confirm-screen-section-column', [ t('to') ]),
h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', 'New Contract'),
h('div.confirm-screen-row-info', t('newContract')),
]),
]),
@ -335,12 +336,12 @@ ConfirmDeployContract.prototype.render = function () {
onSubmit: this.onSubmit,
}, [
// Cancel Button
h('div.cancel.btn-light.confirm-screen-cancel-button', {
h('div.cancel.btn-light.confirm-screen-cancel-button.allcaps', {
onClick: (event) => this.cancel(event, txMeta),
}, 'CANCEL'),
}, t('cancel')),
// Accept Button
h('button.confirm-screen-confirm-button', ['CONFIRM']),
h('button.confirm-screen-confirm-button.allcaps', [t('confirm')]),
]),
])

@ -9,6 +9,7 @@ const ethUtil = require('ethereumjs-util')
const BN = ethUtil.BN
const hexToBn = require('../../../../app/scripts/lib/hex-to-bn')
const { conversionUtil, addCurrencies } = require('../../conversion-util')
const t = require('../../../i18n')
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
@ -165,7 +166,7 @@ ConfirmSendEther.prototype.getData = function () {
},
to: {
address: txParams.to,
name: identities[txParams.to] ? identities[txParams.to].name : 'New Recipient',
name: identities[txParams.to] ? identities[txParams.to].name : t('newRecipient'),
},
memo: txParams.memo || '',
gasFeeInFIAT,
@ -266,7 +267,7 @@ ConfirmSendEther.prototype.render = function () {
h('div.confirm-screen-rows', [
h('section.flex-row.flex-center.confirm-screen-row', [
h('span.confirm-screen-label.confirm-screen-section-column', [ 'From' ]),
h('span.confirm-screen-label.confirm-screen-section-column', [ t('from') ]),
h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', fromName),
h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`),
@ -274,7 +275,7 @@ ConfirmSendEther.prototype.render = function () {
]),
h('section.flex-row.flex-center.confirm-screen-row', [
h('span.confirm-screen-label.confirm-screen-section-column', [ 'To' ]),
h('span.confirm-screen-label.confirm-screen-section-column', [ t('to') ]),
h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', toName),
h('div.confirm-screen-row-detail', `...${toAddress.slice(toAddress.length - 4)}`),
@ -282,7 +283,7 @@ ConfirmSendEther.prototype.render = function () {
]),
h('section.flex-row.flex-center.confirm-screen-row', [
h('span.confirm-screen-label.confirm-screen-section-column', [ 'Gas Fee' ]),
h('span.confirm-screen-label.confirm-screen-section-column', [ t('gasFee') ]),
h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', `${gasFeeInFIAT} ${currentCurrency.toUpperCase()}`),
@ -293,8 +294,8 @@ ConfirmSendEther.prototype.render = function () {
h('section.flex-row.flex-center.confirm-screen-total-box ', [
h('div.confirm-screen-section-column', [
h('span.confirm-screen-label', [ 'Total ' ]),
h('div.confirm-screen-total-box__subtitle', [ 'Amount + Gas' ]),
h('span.confirm-screen-label', [ t('total') + ' ' ]),
h('div.confirm-screen-total-box__subtitle', [ t('amountPlusGas') ]),
]),
h('div.confirm-screen-section-column', [
@ -389,15 +390,15 @@ ConfirmSendEther.prototype.render = function () {
onSubmit: this.onSubmit,
}, [
// Cancel Button
h('div.cancel.btn-light.confirm-screen-cancel-button', {
h('div.cancel.btn-light.confirm-screen-cancel-button.allcaps', {
onClick: (event) => {
clearSend()
this.cancel(event, txMeta)
},
}, 'CANCEL'),
}, t('cancel')),
// Accept Button
h('button.confirm-screen-confirm-button', ['CONFIRM']),
h('button.confirm-screen-confirm-button.allcaps', [t('confirm')]),
]),
])
)
@ -412,7 +413,7 @@ ConfirmSendEther.prototype.onSubmit = function (event) {
if (valid && this.verifyGasParams()) {
this.props.sendTransaction(txMeta, event)
} else {
this.props.dispatch(actions.displayWarning('Invalid Gas Parameters'))
this.props.dispatch(actions.displayWarning(t('invalidGasParams')))
this.setState({ submitting: false })
}
}

@ -6,6 +6,7 @@ const tokenAbi = require('human-standard-token-abi')
const abiDecoder = require('abi-decoder')
abiDecoder.addABI(tokenAbi)
const actions = require('../../actions')
const t = require('../../../i18n')
const clone = require('clone')
const Identicon = require('../identicon')
const ethUtil = require('ethereumjs-util')
@ -133,7 +134,7 @@ ConfirmSendToken.prototype.getAmount = function () {
? +(sendTokenAmount * tokenExchangeRate * conversionRate).toFixed(2)
: null,
token: typeof value === 'undefined'
? 'Unknown'
? t('unknown')
: +sendTokenAmount.toFixed(decimals),
}
@ -204,7 +205,7 @@ ConfirmSendToken.prototype.getData = function () {
},
to: {
address: value,
name: identities[value] ? identities[value].name : 'New Recipient',
name: identities[value] ? identities[value].name : t('newRecipient'),
},
memo: txParams.memo || '',
}
@ -244,7 +245,7 @@ ConfirmSendToken.prototype.renderGasFee = function () {
return (
h('section.flex-row.flex-center.confirm-screen-row', [
h('span.confirm-screen-label.confirm-screen-section-column', [ 'Gas Fee' ]),
h('span.confirm-screen-label.confirm-screen-section-column', [ t('gasFee') ]),
h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', `${fiatGas} ${currentCurrency}`),
@ -266,8 +267,8 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
? (
h('section.flex-row.flex-center.confirm-screen-total-box ', [
h('div.confirm-screen-section-column', [
h('span.confirm-screen-label', [ 'Total ' ]),
h('div.confirm-screen-total-box__subtitle', [ 'Amount + Gas' ]),
h('span.confirm-screen-label', [ t('total') + ' ' ]),
h('div.confirm-screen-total-box__subtitle', [ t('amountPlusGas') ]),
]),
h('div.confirm-screen-section-column', [
@ -279,13 +280,13 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () {
: (
h('section.flex-row.flex-center.confirm-screen-total-box ', [
h('div.confirm-screen-section-column', [
h('span.confirm-screen-label', [ 'Total ' ]),
h('div.confirm-screen-total-box__subtitle', [ 'Amount + Gas' ]),
h('span.confirm-screen-label', [ t('total') + ' ' ]),
h('div.confirm-screen-total-box__subtitle', [ t('amountPlusGas') ]),
]),
h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', `${tokenAmount} ${symbol}`),
h('div.confirm-screen-row-detail', `+ ${fiatGas} ${currentCurrency} Gas`),
h('div.confirm-screen-row-detail', `+ ${fiatGas} ${currentCurrency} ${t('gas')}`),
]),
])
)
@ -314,9 +315,9 @@ ConfirmSendToken.prototype.render = function () {
h('div.page-container__header', [
h('button.confirm-screen-back-button', {
onClick: () => editTransaction(txMeta),
}, 'Edit'),
h('div.page-container__title', 'Confirm'),
h('div.page-container__subtitle', `Please review your transaction.`),
}, t('edit')),
h('div.page-container__title', t('confirm')),
h('div.page-container__subtitle', t('pleaseReviewTransaction')),
]),
h('div.flex-row.flex-center.confirm-screen-identicons', [
h('div.confirm-screen-account-wrapper', [
@ -357,7 +358,7 @@ ConfirmSendToken.prototype.render = function () {
h('div.confirm-screen-rows', [
h('section.flex-row.flex-center.confirm-screen-row', [
h('span.confirm-screen-label.confirm-screen-section-column', [ 'From' ]),
h('span.confirm-screen-label.confirm-screen-section-column', [ t('from') ]),
h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', fromName),
h('div.confirm-screen-row-detail', `...${fromAddress.slice(fromAddress.length - 4)}`),
@ -365,7 +366,7 @@ ConfirmSendToken.prototype.render = function () {
]),
toAddress && h('section.flex-row.flex-center.confirm-screen-row', [
h('span.confirm-screen-label.confirm-screen-section-column', [ 'To' ]),
h('span.confirm-screen-label.confirm-screen-section-column', [ t('to') ]),
h('div.confirm-screen-section-column', [
h('div.confirm-screen-row-info', toName),
h('div.confirm-screen-row-detail', `...${toAddress.slice(toAddress.length - 4)}`),
@ -383,12 +384,12 @@ ConfirmSendToken.prototype.render = function () {
onSubmit: this.onSubmit,
}, [
// Cancel Button
h('div.cancel.btn-light.confirm-screen-cancel-button', {
h('div.cancel.btn-light.confirm-screen-cancel-button.allcaps', {
onClick: (event) => this.cancel(event, txMeta),
}, 'CANCEL'),
}, t('cancel')),
// Accept Button
h('button.confirm-screen-confirm-button', ['CONFIRM']),
h('button.confirm-screen-confirm-button.allcaps', [t('confirm')]),
]),
@ -405,7 +406,7 @@ ConfirmSendToken.prototype.onSubmit = function (event) {
if (valid && this.verifyGasParams()) {
this.props.sendTransaction(txMeta, event)
} else {
this.props.dispatch(actions.displayWarning('Invalid Gas Parameters'))
this.props.dispatch(actions.displayWarning(t('invalidGasParams')))
this.setState({ submitting: false })
}
}

@ -4,6 +4,7 @@ const inherits = require('util').inherits
const AccountPanel = require('./account-panel')
const TypedMessageRenderer = require('./typed-message-renderer')
const t = require('../../i18n')
module.exports = PendingMsgDetails
@ -45,7 +46,7 @@ PendingMsgDetails.prototype.render = function () {
height: '260px',
},
}, [
h('label.font-small', { style: { display: 'block' } }, 'YOU ARE SIGNING'),
h('label.font-small.allcaps', { style: { display: 'block' } }, t('youSign')),
h(TypedMessageRenderer, {
value: data,
style: {

@ -2,6 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const PendingTxDetails = require('./pending-typed-msg-details')
const t = require('../../i18n')
module.exports = PendingMsg
@ -26,19 +27,19 @@ PendingMsg.prototype.render = function () {
fontWeight: 'bold',
textAlign: 'center',
},
}, 'Sign Message'),
}, t('signMessage')),
// message details
h(PendingTxDetails, state),
// sign + cancel
h('.flex-row.flex-space-around', [
h('button', {
h('button.allcaps', {
onClick: state.cancelTypedMessage,
}, 'Cancel'),
h('button', {
}, t('cancel')),
h('button.allcaps', {
onClick: state.signTypedMessage,
}, 'Sign'),
}, t('sign')),
]),
])

@ -7,6 +7,7 @@ const inherits = require('util').inherits
const actions = require('../../actions')
const selectors = require('../../selectors')
const { isValidAddress, allNull } = require('../../util')
const t = require('../../../i18n')
// const BalanceComponent = require('./balance-component')
const Identicon = require('../identicon')
@ -126,14 +127,14 @@ SendTokenScreen.prototype.validate = function () {
const amount = Number(stringAmount)
const errors = {
to: !to ? 'Required' : null,
amount: !amount ? 'Required' : null,
gasPrice: !gasPrice ? 'Gas Price Required' : null,
gasLimit: !gasLimit ? 'Gas Limit Required' : null,
to: !to ? t('required') : null,
amount: !amount ? t('required') : null,
gasPrice: !gasPrice ? t('gasPriceRequired') : null,
gasLimit: !gasLimit ? t('gasLimitRequired') : null,
}
if (to && !isValidAddress(to)) {
errors.to = 'Invalid address'
errors.to = t('invalidAddress')
}
const isValid = Object.entries(errors).every(([key, value]) => value === null)
@ -233,11 +234,11 @@ SendTokenScreen.prototype.renderToAddressInput = function () {
'send-screen-input-wrapper--error': errorMessage,
}),
}, [
h('div', ['To:']),
h('div', [t('to') + ':']),
h('input.large-input.send-screen-input', {
name: 'address',
list: 'addresses',
placeholder: 'Address',
placeholder: t('address'),
value: to,
onChange: e => this.setState({
to: e.target.value,
@ -290,7 +291,7 @@ SendTokenScreen.prototype.renderAmountInput = function () {
}),
}, [
h('div.send-screen-amount-labels', [
h('span', ['Amount']),
h('span', [t('amount')]),
h(CurrencyToggle, {
currentCurrency: tokenExchangeRate ? selectedCurrency : 'USD',
currencies: tokenExchangeRate ? [ symbol, 'USD' ] : [],
@ -355,8 +356,8 @@ SendTokenScreen.prototype.renderGasInput = function () {
}),
h('div.send-screen-gas-labels', {}, [
h('span', [ h('i.fa.fa-bolt'), 'Gas fee:']),
h('span', ['What\'s this?']),
h('span', [ h('i.fa.fa-bolt'), t('gasFee') + ':']),
h('span', [t('whatsThis')]),
]),
h('div.large-input.send-screen-gas-input', [
h(GasFeeDisplay, {
@ -370,7 +371,7 @@ SendTokenScreen.prototype.renderGasInput = function () {
h(
'div.send-screen-gas-input-customize',
{ onClick: () => this.setState({ isGasTooltipOpen: !isGasTooltipOpen }) },
['Customize']
[t('customize')]
),
]),
h('div.send-screen-input-wrapper__error-message', [
@ -381,7 +382,7 @@ SendTokenScreen.prototype.renderGasInput = function () {
SendTokenScreen.prototype.renderMemoInput = function () {
return h('div.send-screen-input-wrapper', [
h('div', {}, ['Transaction memo (optional)']),
h('div', {}, [t('transactionMemo')]),
h(
'input.large-input.send-screen-input',
{ onChange: e => this.setState({ memo: e.target.value }) }
@ -397,10 +398,10 @@ SendTokenScreen.prototype.renderButtons = function () {
h('button.send-token__button-next.btn-secondary', {
className: !isValid && 'send-screen__send-button__disabled',
onClick: () => isValid && this.submit(),
}, ['Next']),
}, [t('next')]),
h('button.send-token__button-cancel.btn-tertiary', {
onClick: () => backToAccountDetail(selectedAddress),
}, ['Cancel']),
}, [t('cancel')]),
])
}
@ -417,9 +418,9 @@ SendTokenScreen.prototype.render = function () {
diameter: 75,
address: selectedTokenAddress,
}),
h('div.send-token__title', ['Send Tokens']),
h('div.send-token__description', ['Send Tokens to anyone with an Ethereum account']),
h('div.send-token__balance-text', ['Your Token Balance is:']),
h('div.send-token__title', [t('sendTokens')]),
h('div.send-token__description', [t('sendTokensAnywhere')]),
h('div.send-token__balance-text', [t('tokenBalance')]),
h('div.send-token__token-balance', [
h(TokenBalance, { token: selectedToken, balanceOnly: true }),
]),

@ -2,6 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const CurrencyDisplay = require('./currency-display')
const t = require('../../../i18n')
module.exports = GasFeeDisplay
@ -30,7 +31,7 @@ GasFeeDisplay.prototype.render = function () {
convertedPrefix: '$',
readOnly: true,
})
: h('div.currency-display', 'Loading...'),
: h('div.currency-display', t('loading')),
h('button.send-v2__sliders-icon-container', {
onClick,
@ -41,4 +42,3 @@ GasFeeDisplay.prototype.render = function () {
])
}

@ -2,6 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const InputNumber = require('../input-number.js')
const t = require('../../../i18n')
module.exports = GasTooltip
@ -81,7 +82,7 @@ GasTooltip.prototype.render = function () {
'marginTop': '81px',
},
}, [
h('span.gas-tooltip-label', {}, ['Gas Limit']),
h('span.gas-tooltip-label', {}, [t('gasLimit')]),
h('i.fa.fa-info-circle'),
]),
h(InputNumber, {
@ -97,4 +98,3 @@ GasTooltip.prototype.render = function () {
]),
])
}

@ -2,6 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const AccountListItem = require('./account-list-item')
const t = require('../../../i18n')
module.exports = ToAutoComplete
@ -92,7 +93,7 @@ ToAutoComplete.prototype.render = function () {
return h('div.send-v2__to-autocomplete', {}, [
h('input.send-v2__to-autocomplete__input', {
placeholder: 'Recipient Address',
placeholder: t('recipientAddress'),
className: inError ? `send-v2__error-border` : '',
value: to,
onChange: event => onChange(event.target.value),
@ -111,4 +112,3 @@ ToAutoComplete.prototype.render = function () {
])
}

@ -7,6 +7,7 @@ const { qrcode } = require('qrcode-npm')
const { shapeShiftSubview, pairUpdate, buyWithShapeShift } = require('../actions')
const { isValidAddress } = require('../util')
const SimpleDropdown = require('./dropdowns/simple-dropdown')
const t = require('../../i18n')
function mapStateToProps (state) {
const {
@ -93,7 +94,7 @@ ShapeshiftForm.prototype.onBuyWithShapeShift = function () {
}))
.catch(() => this.setState({
showQrCode: false,
errorMessage: 'Invalid Request',
errorMessage: t('invalidRequest'),
isLoading: false,
}))
}
@ -125,10 +126,10 @@ ShapeshiftForm.prototype.renderMarketInfo = function () {
return h('div.shapeshift-form__metadata', {}, [
this.renderMetadata('Status', limit ? 'Available' : 'Unavailable'),
this.renderMetadata('Limit', limit),
this.renderMetadata('Exchange Rate', rate),
this.renderMetadata('Minimum', minimum),
this.renderMetadata(t('status'), limit ? t('available') : t('unavailable')),
this.renderMetadata(t('limit'), limit),
this.renderMetadata(t('exchangeRate'), rate),
this.renderMetadata(t('min'), minimum),
])
}
@ -142,7 +143,7 @@ ShapeshiftForm.prototype.renderQrCode = function () {
return h('div.shapeshift-form', {}, [
h('div.shapeshift-form__deposit-instruction', [
`Deposit your ${depositCoin.toUpperCase()} to the address below:`,
t('depositCoin', [depositCoin.toUpperCase()]),
]),
h('div', depositAddress),
@ -179,7 +180,7 @@ ShapeshiftForm.prototype.render = function () {
h('div.shapeshift-form__selector', [
h('div.shapeshift-form__selector-label', 'Deposit'),
h('div.shapeshift-form__selector-label', t('deposit')),
h(SimpleDropdown, {
selectedOption: this.state.depositCoin,
@ -199,7 +200,7 @@ ShapeshiftForm.prototype.render = function () {
h('div.shapeshift-form__selector', [
h('div.shapeshift-form__selector-label', [
'Receive',
t('receive'),
]),
h('div.shapeshift-form__selector-input', ['ETH']),
@ -217,7 +218,7 @@ ShapeshiftForm.prototype.render = function () {
}, [
h('div.shapeshift-form__address-input-label', [
'Your Refund Address',
t('refundAddress'),
]),
h('input.shapeshift-form__address-input', {
@ -239,7 +240,7 @@ ShapeshiftForm.prototype.render = function () {
className: btnClass,
disabled: !token,
onClick: () => this.onBuyWithShapeShift(),
}, ['Buy']),
}, [t('buy')]),
])
}

@ -6,6 +6,7 @@ const vreme = new (require('vreme'))()
const explorerLink = require('etherscan-link').createExplorerLink
const actions = require('../actions')
const addressSummary = require('../util').addressSummary
const t = require('../../i18n')
const CopyButton = require('./copyButton')
const EthBalance = require('./eth-balance')
@ -75,7 +76,7 @@ ShiftListItem.prototype.renderUtilComponents = function () {
value: this.props.depositAddress,
}),
h(Tooltip, {
title: 'QR Code',
title: t('qrCode'),
}, [
h('i.fa.fa-qrcode.pointer.pop-hover', {
onClick: () => props.dispatch(actions.reshowQrCode(props.depositAddress, props.depositType)),
@ -135,8 +136,8 @@ ShiftListItem.prototype.renderInfo = function () {
color: '#ABA9AA',
width: '100%',
},
}, `${props.depositType} to ETH via ShapeShift`),
h('div', 'No deposits received'),
}, t('toETHviaShapeShift', [props.depositType])),
h('div', t('noDeposits')),
h('div', {
style: {
fontSize: 'x-small',
@ -158,8 +159,8 @@ ShiftListItem.prototype.renderInfo = function () {
color: '#ABA9AA',
width: '100%',
},
}, `${props.depositType} to ETH via ShapeShift`),
h('div', 'Conversion in progress'),
}, t('toETHviaShapeShift', [props.depositType])),
h('div', t('conversionProgress')),
h('div', {
style: {
fontSize: 'x-small',
@ -184,7 +185,7 @@ ShiftListItem.prototype.renderInfo = function () {
color: '#ABA9AA',
width: '100%',
},
}, 'From ShapeShift'),
}, t('fromShapeShift')),
h('div', formatDate(props.time)),
h('div', {
style: {
@ -196,7 +197,7 @@ ShiftListItem.prototype.renderInfo = function () {
])
case 'failed':
return h('span.error', '(Failed)')
return h('span.error', '(' + t('failed') + ')')
default:
return ''
}

@ -9,6 +9,7 @@ const classnames = require('classnames')
const AccountDropdownMini = require('./dropdowns/account-dropdown-mini')
const actions = require('../actions')
const t = require('../../i18n')
const { conversionUtil } = require('../conversion-util')
const {
@ -54,7 +55,7 @@ SignatureRequest.prototype.renderHeader = function () {
h('div.request-signature__header-background'),
h('div.request-signature__header__text', 'Signature Request'),
h('div.request-signature__header__text', t('sigRequest')),
h('div.request-signature__header__tip-container', [
h('div.request-signature__header__tip'),
@ -75,7 +76,7 @@ SignatureRequest.prototype.renderAccountDropdown = function () {
return h('div.request-signature__account', [
h('div.request-signature__account-text', ['Account:']),
h('div.request-signature__account-text', [t('account') + ':']),
h(AccountDropdownMini, {
selectedAccount,
@ -102,7 +103,7 @@ SignatureRequest.prototype.renderBalance = function () {
return h('div.request-signature__balance', [
h('div.request-signature__balance-text', ['Balance:']),
h('div.request-signature__balance-text', [t('balance')]),
h('div.request-signature__balance-value', `${balanceInEther} ETH`),
@ -136,7 +137,7 @@ SignatureRequest.prototype.renderRequestInfo = function () {
return h('div.request-signature__request-info', [
h('div.request-signature__headline', [
`Your signature is being requested`,
t('yourSigRequested'),
]),
])
@ -154,21 +155,18 @@ SignatureRequest.prototype.msgHexToText = function (hex) {
SignatureRequest.prototype.renderBody = function () {
let rows
let notice = 'You are signing:'
let notice = t('youSign') + ':'
const { txData } = this.props
const { type, msgParams: { data } } = txData
if (type === 'personal_sign') {
rows = [{ name: 'Message', value: this.msgHexToText(data) }]
rows = [{ name: t('message'), value: this.msgHexToText(data) }]
} else if (type === 'eth_signTypedData') {
rows = data
} else if (type === 'eth_sign') {
rows = [{ name: 'Message', value: data }]
notice = `Signing this message can have
dangerous side effects. Only sign messages from
sites you fully trust with your entire account.
This dangerous method will be removed in a future version. `
rows = [{ name: t('message'), value: data }]
notice = t('signNotice')
}
return h('div.request-signature__body', {}, [
@ -227,10 +225,10 @@ SignatureRequest.prototype.renderFooter = function () {
return h('div.request-signature__footer', [
h('button.request-signature__footer__cancel-button', {
onClick: cancel,
}, 'CANCEL'),
}, t('cancel')),
h('button.request-signature__footer__sign-button', {
onClick: sign,
}, 'SIGN'),
}, t('sign')),
])
}
@ -250,4 +248,3 @@ SignatureRequest.prototype.render = function () {
)
}

@ -5,6 +5,7 @@ const TokenTracker = require('eth-token-tracker')
const TokenCell = require('./token-cell.js')
const connect = require('react-redux').connect
const selectors = require('../selectors')
const t = require('../../i18n')
function mapStateToProps (state) {
return {
@ -42,7 +43,7 @@ TokenList.prototype.render = function () {
const { tokens, isLoading, error } = state
if (isLoading) {
return this.message('Loading Tokens...')
return this.message(t('loadingTokens'))
}
if (error) {
@ -52,7 +53,7 @@ TokenList.prototype.render = function () {
padding: '80px',
},
}, [
'We had trouble loading your token balances. You can view them ',
t('troubleTokenBalances'),
h('span.hotFix', {
style: {
color: 'rgba(247, 134, 28, 1)',
@ -63,7 +64,7 @@ TokenList.prototype.render = function () {
url: `https://ethplorer.io/address/${userAddress}`,
})
},
}, 'here'),
}, t('here')),
])
}

@ -11,6 +11,7 @@ const vreme = new (require('vreme'))()
const Tooltip = require('./tooltip')
const numberToBN = require('number-to-bn')
const actions = require('../actions')
const t = require('../../i18n')
const TransactionIcon = require('./transaction-list-item-icon')
const ShiftListItem = require('./shift-list-item')
@ -85,7 +86,7 @@ TransactionListItem.prototype.render = function () {
]),
h(Tooltip, {
title: 'Transaction Number',
title: t('transactionNumber'),
position: 'right',
}, [
h('span', {
@ -142,12 +143,12 @@ TransactionListItem.prototype.render = function () {
style: {
paddingRight: '2px',
},
}, 'Taking too long?'),
}, t('takesTooLong')),
h('div', {
style: {
textDecoration: 'underline',
},
}, 'Retry with a higher gas price here'),
}, t('retryWithMoreGas')),
]),
])
)
@ -176,11 +177,11 @@ function recipientField (txParams, transaction, isTx, isMsg) {
let message
if (isMsg) {
message = 'Signature Requested'
message = t('sigRequested')
} else if (txParams.to) {
message = addressSummary(txParams.to)
} else {
message = 'Contract Deployment'
message = t('contractDeployment')
}
return h('div', {
@ -203,7 +204,7 @@ function renderErrorOrWarning (transaction) {
// show rejected
if (status === 'rejected') {
return h('span.error', ' (Rejected)')
return h('span.error', ' (' + t('rejected') + ')')
}
if (transaction.err || transaction.warning) {
const { err, warning = {} } = transaction
@ -219,7 +220,7 @@ function renderErrorOrWarning (transaction) {
title: message,
position: 'bottom',
}, [
h(`span.error`, ` (Failed)`),
h(`span.error`, ` (` + t('failed') + `)`),
])
)
}
@ -231,7 +232,7 @@ function renderErrorOrWarning (transaction) {
title: message,
position: 'bottom',
}, [
h(`span.warning`, ` (Warning)`),
h(`span.warning`, ` (` + t('warning') + `)`),
])
}
}

@ -3,6 +3,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const TransactionListItem = require('./transaction-list-item')
const t = require('../../i18n')
module.exports = TransactionList
@ -78,10 +79,9 @@ TransactionList.prototype.render = function () {
style: {
marginTop: '50px',
},
}, 'No transaction history.'),
}, t('noTransactionHistory')),
]),
]),
])
)
}

@ -13,6 +13,7 @@ const { conversionUtil, multiplyCurrencies } = require('../conversion-util')
const { calcTokenAmount } = require('../token-util')
const { getCurrentCurrency } = require('../selectors')
const t = require('../../i18n')
module.exports = connect(mapStateToProps)(TxListItem)
@ -63,7 +64,7 @@ TxListItem.prototype.getAddressText = function () {
default:
return address
? `${address.slice(0, 10)}...${address.slice(-4)}`
: 'Contract Deployment'
: t('contractDeployment')
}
}

@ -10,6 +10,7 @@ const { formatDate } = require('../util')
const { showConfTxPage } = require('../actions')
const classnames = require('classnames')
const { tokenInfoGetter } = require('../token-util')
const t = require('../../i18n')
module.exports = connect(mapStateToProps, mapDispatchToProps)(TxList)
@ -56,7 +57,7 @@ TxList.prototype.renderTransaction = function () {
: [h(
'div.tx-list-item.tx-list-item--empty',
{ key: 'tx-list-none' },
[ 'No Transactions' ],
[ t('noTransactions') ],
)]
}
@ -107,7 +108,7 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa
if (isUnapproved) {
opts.onClick = () => showConfTxPage({id: transActionId})
opts.transactionStatus = 'Not Started'
opts.transactionStatus = t('Not Started')
} else if (transactionHash) {
opts.onClick = () => this.view(transactionHash, transactionNetworkId)
}

@ -5,6 +5,7 @@ const ethUtil = require('ethereumjs-util')
const inherits = require('util').inherits
const actions = require('../actions')
const selectors = require('../selectors')
const t = require('../../i18n')
const BalanceComponent = require('./balance-component')
const TxList = require('./tx-list')
@ -68,25 +69,25 @@ TxView.prototype.renderButtons = function () {
return !selectedToken
? (
h('div.flex-row.flex-center.hero-balance-buttons', [
h('button.btn-clear.hero-balance-button', {
h('button.btn-clear.hero-balance-button.allcaps', {
onClick: () => showModal({
name: 'DEPOSIT_ETHER',
}),
}, 'DEPOSIT'),
}, t('deposit')),
h('button.btn-clear.hero-balance-button', {
h('button.btn-clear.hero-balance-button.allcaps', {
style: {
marginLeft: '0.8em',
},
onClick: showSendPage,
}, 'SEND'),
}, t('send')),
])
)
: (
h('div.flex-row.flex-center.hero-balance-buttons', [
h('button.btn-clear.hero-balance-button', {
onClick: showSendTokenPage,
}, 'SEND'),
}, t('send')),
])
)
}
@ -100,9 +101,10 @@ TxView.prototype.render = function () {
h('div.flex-row.phone-visible', {
style: {
margin: '1.5em 1.2em 0',
justifyContent: 'space-between',
alignItems: 'center',
flex: '0 0 auto',
margin: '10px',
},
}, [
@ -110,11 +112,10 @@ TxView.prototype.render = function () {
style: {
fontSize: '1.3em',
cursor: 'pointer',
padding: '10px',
},
onClick: () => {
this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar()
},
}, []),
onClick: () => this.props.sidebarOpen ? this.props.hideSidebar() : this.props.showSidebar(),
}),
h('.identicon-wrapper.select-none', {
style: {

@ -11,6 +11,7 @@ const actions = require('../actions')
const BalanceComponent = require('./balance-component')
const TokenList = require('./token-list')
const selectors = require('../selectors')
const t = require('../../i18n')
module.exports = connect(mapStateToProps, mapDispatchToProps)(WalletView)
@ -116,7 +117,7 @@ WalletView.prototype.render = function () {
onClick: hideSidebar,
}),
h('div.wallet-view__keyring-label', isLoose ? 'IMPORTED' : ''),
h('div.wallet-view__keyring-label.allcaps', isLoose ? t('imported') : ''),
h('div.flex-column.flex-center.wallet-view__name-container', {
style: { margin: '0 auto' },
@ -133,13 +134,13 @@ WalletView.prototype.render = function () {
selectedIdentity.name,
]),
h('button.btn-clear.wallet-view__details-button', 'DETAILS'),
h('button.btn-clear.wallet-view__details-button.allcaps', t('details')),
]),
]),
h(Tooltip, {
position: 'bottom',
title: this.state.hasCopied ? 'Copied!' : 'Copy to clipboard',
title: this.state.hasCopied ? t('copiedExclamation') : t('copyToClipboard'),
wrapperClassName: 'wallet-view__tooltip',
}, [
h('button.wallet-view__address', {
@ -172,7 +173,7 @@ WalletView.prototype.render = function () {
showAddTokenPage()
hideSidebar()
},
}, 'Add Token'),
}, t('addToken')),
])
}

@ -5,9 +5,6 @@
flex-direction: column;
justify-content: flex-start;
align-items: center;
margin: .3em .9em 0;
// height: 80vh;
// max-height: 225px;
flex: 0 0 auto;
}

@ -55,3 +55,6 @@
@import './new-account.scss';
@import './tooltip.scss';
@import './welcome-screen.scss';

@ -641,32 +641,40 @@
&__buy-rows {
width: 100%;
padding: 33px;
padding-top: 0px;
padding: 0 30px;
display: flex;
flex-flow: column nowrap;
flex: 1;
overflow-y: auto;
@media screen and (max-width: 575px) {
height: 0;
}
}
&__logo {
height: 60px;
background-repeat: no-repeat;
background-size: contain;
background-position: center;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
&__buy-row {
border-bottom: 1px solid $alto;
display: flex;
justify-content: space-between;
align-items: center;
flex: 1;
padding-bottom: 25px;
padding-top: 25px;
flex: 1 0 auto;
padding: 30px 0 20px;
min-height: 170px;
@media screen and (max-width: 575px) {
min-height: 360px;
min-height: 270px;
flex-flow: column;
justify-content: center;
padding-top: 45px;
justify-content: flex-start;
}
&__back {
@ -692,30 +700,35 @@
}
}
&__logo {
&__logo-container {
display: flex;
justify-content: center;
flex: 0.3 1 auto;
flex: 0 0 auto;
padding: 0 20px;
@media screen and (min-width: 575px) {
min-width: 215px;
@media screen and (min-width: 576px) {
width: 12rem;
}
@media screen and (max-width: 575px) {
width: 100%;
max-height: 6rem;
padding-bottom: 20px;
}
}
&__coinbase-logo {
height: 40px;
width: 180px;
}
&__shapeshift-logo {
height: 60px;
width: 174px;
}
&__eth-logo {
border-radius: 50%;
width: 68px;
height: 68px;
width: 68px;
border: 3px solid $tundora;
z-index: 25;
padding: 4px;
@ -728,10 +741,11 @@
&__description {
color: $cape-cod;
flex: 0.5 1 auto;
padding-bottom: 20px;
align-self: flex-start;
@media screen and (min-width: 575px) {
min-width: 315px;
width: 15rem;
}
&__title {

@ -161,15 +161,14 @@
display: flex;
flex-flow: column;
align-items: center;
padding: 30px 30px 0;
&__input-label {
color: $scorpion;
font-family: Roboto;
font-size: 16px;
line-height: 21px;
margin-top: 29px;
align-self: flex-start;
margin-left: 30px;
}
&__input {

@ -51,6 +51,7 @@ $wallet-view-bg: $alabaster;
cursor: pointer;
display: flex;
justify-content: center;
padding: 10px;
}
// wallet view and sidebar
@ -291,7 +292,6 @@ $wallet-view-bg: $alabaster;
padding-right: 6px;
}
// first time
.first-view-main {
display: flex;
@ -314,3 +314,22 @@ $wallet-view-bg: $alabaster;
width: 62vw;
}
}
.unlock-screen-container {
z-index: $main-container-z-index;
font-family: Roboto;
display: flex;
justify-content: center;
align-items: center;
flex: 1 0 auto;
background: #f7f7f7;
width: 100%;
}
.unlock-screen {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1 0 auto;
}

@ -0,0 +1,59 @@
.welcome-screen {
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
font-family: Roboto;
font-weight: 400;
width: 100%;
flex: 1 0 auto;
padding: 70px 0;
background: $white;
@media screen and (max-width: 575px) {
padding: 0;
}
&__info {
display: flex;
flex-flow: column;
width: 100%;
height: 100%;
align-items: center;
&__header {
font-size: 1.65em;
margin-bottom: 14px;
@media screen and (max-width: 575px) {
font-size: 1.5em;
}
}
&__copy {
font-size: 1em;
width: 400px;
max-width: 90vw;
text-align: center;
@media screen and (max-width: 575px) {
font-size: 0.9em;
}
}
}
&__button {
height: 54px;
width: 198px;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.14);
color: #FFFFFF;
font-size: 20px;
font-weight: 500;
line-height: 26px;
text-align: center;
text-transform: uppercase;
margin: 35px 0 14px;
transition: 200ms ease-in-out;
background-color: rgba(247, 134, 28, 0.9);
}
}

@ -70,6 +70,10 @@ input.large-input {
height: 36px;
}
.allcaps {
text-transform: uppercase;
}
.page-container {
width: 400px;
background-color: $white;
@ -77,25 +81,30 @@ input.large-input {
z-index: 25;
display: flex;
flex-flow: column;
border-radius: 7px;
height: 100%;
&__header {
display: flex;
flex-flow: column;
border-bottom: 1px solid $geyser;
padding: 1.15rem 0.95rem;
padding: 20px;
flex: 0 0 auto;
background: $alabaster;
position: relative;
}
&__header-close::after {
content: '\00D7';
font-size: 40px;
&__header-close {
color: $tundora;
position: absolute;
top: 21.5px;
right: 28.5px;
top: 20px;
right: 20px;
cursor: pointer;
overflow: hidden;
&::after {
content: '\00D7';
font-size: 40px;
}
}
&__footer {
@ -114,7 +123,7 @@ input.large-input {
&__footer-button {
width: 165px;
height: 60px;
height: 55px;
font-size: 1rem;
text-transform: uppercase;
margin-right: 1rem;
@ -130,7 +139,7 @@ input.large-input {
font-family: Roboto;
font-size: 2rem;
font-weight: 500;
line-height: initial;
line-height: 2rem;
}
&__subtitle {
@ -174,6 +183,15 @@ input.large-input {
}
}
}
&--full-width {
width: initial;
}
&__content {
height: 100%;
overflow-y: auto;
}
}
@media screen and (max-width: 250px) {
@ -200,5 +218,6 @@ input.large-input {
width: 100%;
overflow-y: auto;
background-color: $white;
border-radius: 0;
}
}

@ -6,6 +6,7 @@ const h = require('react-hyperscript')
const Mascot = require('../components/mascot')
const actions = require('../actions')
const Tooltip = require('../components/tooltip')
const t = require('../../i18n')
const getCaretCoordinates = require('textarea-caret')
const environmentType = require('../../../app/scripts/lib/environment-type')
const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/config').enums
@ -59,7 +60,7 @@ InitializeMenuScreen.prototype.renderMenu = function (state) {
color: '#7F8082',
marginBottom: 10,
},
}, 'MetaMask'),
}, t('appName')),
h('div', [
@ -69,10 +70,10 @@ InitializeMenuScreen.prototype.renderMenu = function (state) {
color: '#7F8082',
display: 'inline',
},
}, 'Encrypt your new DEN'),
}, t('encryptNewDen')),
h(Tooltip, {
title: 'Your DEN is your password-encrypted storage within MetaMask.',
title: t('denExplainer'),
}, [
h('i.fa.fa-question-circle.pointer', {
style: {
@ -92,7 +93,7 @@ InitializeMenuScreen.prototype.renderMenu = function (state) {
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box',
placeholder: 'New Password (min 8 chars)',
placeholder: t('newPassword'),
onInput: this.inputChanged.bind(this),
style: {
width: 260,
@ -104,7 +105,7 @@ InitializeMenuScreen.prototype.renderMenu = function (state) {
h('input.large-input.letter-spacey', {
type: 'password',
id: 'password-box-confirm',
placeholder: 'Confirm Password',
placeholder: t('confirmPassword'),
onKeyPress: this.createVaultOnEnter.bind(this),
onInput: this.inputChanged.bind(this),
style: {
@ -119,7 +120,7 @@ InitializeMenuScreen.prototype.renderMenu = function (state) {
style: {
margin: 12,
},
}, 'Create'),
}, t('createDen')),
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
@ -129,7 +130,7 @@ InitializeMenuScreen.prototype.renderMenu = function (state) {
color: 'rgb(247, 134, 28)',
textDecoration: 'underline',
},
}, 'Import Existing DEN'),
}, t('importDen')),
]),
h('.flex-row.flex-center.flex-grow', [
@ -178,12 +179,12 @@ InitializeMenuScreen.prototype.createNewVaultAndKeychain = function () {
var passwordConfirm = passwordConfirmBox.value
if (password.length < 8) {
this.warning = 'password not long enough'
this.warning = t('passwordShort')
this.props.dispatch(actions.displayWarning(this.warning))
return
}
if (password !== passwordConfirm) {
this.warning = 'passwords don\'t match'
this.warning = t('passwordMismatch')
this.props.dispatch(actions.displayWarning(this.warning))
return
}

@ -144,6 +144,19 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () {
// check seed
var seedBox = document.querySelector('textarea.twelve-word-phrase')
var seed = seedBox.value.trim()
// true if the string has more than a space between words.
if (seed.split(' ').length > 1) {
this.warning = 'there can only be a space between words'
this.props.dispatch(actions.displayWarning(this.warning))
return
}
// true if seed contains a character that is not between a-z or a space
if (!seed.match(/^[a-z ]+$/)) {
this.warning = 'seed words only have lowercase characters'
this.props.dispatch(actions.displayWarning(this.warning))
return
}
if (seed.split(' ').length !== 12) {
this.warning = 'seed phrases are 12 words long'
this.props.dispatch(actions.displayWarning(this.warning))

@ -32,19 +32,7 @@ MainContainer.prototype.render = function () {
return h(Settings, {key: 'config'})
default:
log.debug('rendering locked screen')
contents = {
component: UnlockScreen,
style: {
boxShadow: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#F7F7F7',
// must force 100%, because lock screen is full-width
width: '100%',
},
key: 'locked',
}
return h('.unlock-screen-container', {}, h(UnlockScreen, { key: 'locked' }))
}
}

@ -44,6 +44,7 @@ function reduceMetamask (state, action) {
featureFlags: {},
networkEndpointType: OLD_UI_NETWORK_TYPE,
isRevealingSeedWords: false,
welcomeScreenSeen: false,
}, state.metamask)
switch (action.type) {
@ -347,6 +348,11 @@ function reduceMetamask (state, action) {
networkEndpointType: action.value,
})
case actions.CLOSE_WELCOME_SCREEN:
return extend(metamaskState, {
welcomeScreenSeen: true,
})
default:
return metamaskState

@ -5,6 +5,7 @@ const connect = require('react-redux').connect
const actions = require('./actions')
const getCaretCoordinates = require('textarea-caret')
const EventEmitter = require('events').EventEmitter
const t = require('../i18n')
const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums
const environmentType = require('../../app/scripts/lib/environment-type')
@ -28,12 +29,7 @@ UnlockScreen.prototype.render = function () {
const state = this.props
const warning = state.warning
return (
h('.flex-column', {
style: {
width: 'inherit',
},
}, [
h('.unlock-screen.flex-column.flex-center.flex-grow', [
h('.unlock-screen', [
h(Mascot, {
animationEventEmitter: this.animationEventEmitter,
@ -45,7 +41,7 @@ UnlockScreen.prototype.render = function () {
textTransform: 'uppercase',
color: '#7F8082',
},
}, 'MetaMask'),
}, t('appName')),
h('input.large-input', {
type: 'password',
@ -72,9 +68,7 @@ UnlockScreen.prototype.render = function () {
margin: 10,
},
}, 'Log In'),
]),
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: () => {
this.props.dispatch(actions.markPasswordForgotten())
@ -88,9 +82,7 @@ UnlockScreen.prototype.render = function () {
textDecoration: 'underline',
},
}, 'Restore from seed phrase'),
]),
h('.flex-row.flex-center.flex-grow', [
h('p.pointer', {
onClick: () => {
this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
@ -103,8 +95,6 @@ UnlockScreen.prototype.render = function () {
marginTop: '32px',
},
}, 'Use classic interface'),
]),
])
)
}

@ -0,0 +1,56 @@
import EventEmitter from 'events'
import h from 'react-hyperscript'
import { Component } from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {closeWelcomeScreen} from './actions'
import Mascot from './components/mascot'
class WelcomeScreen extends Component {
static propTypes = {
closeWelcomeScreen: PropTypes.func.isRequired,
}
constructor(props) {
super(props)
this.animationEventEmitter = new EventEmitter()
}
initiateAccountCreation = () => {
this.props.closeWelcomeScreen()
}
render () {
return h('div.welcome-screen', [
h('div.welcome-screen__info', [
h(Mascot, {
animationEventEmitter: this.animationEventEmitter,
width: '225',
height: '225',
}),
h('div.welcome-screen__info__header', 'Welcome to MetaMask Beta'),
h('div.welcome-screen__info__copy', 'MetaMask is a secure identity vault for Ethereum.'),
h('div.welcome-screen__info__copy', `It allows you to hold ether & tokens,
and serves as your bridge to decentralized applications.`),
h('button.welcome-screen__button', {
onClick: this.initiateAccountCreation,
}, 'Continue'),
]),
])
}
}
export default connect(
null,
dispatch => ({
closeWelcomeScreen: () => dispatch(closeWelcomeScreen()),
})
)(WelcomeScreen)

@ -0,0 +1,33 @@
// cross-browser connection to extension i18n API
const chrome = chrome || null
const browser = browser || null
const extension = require('extensionizer')
var log = require('loglevel')
window.log = log
let getMessage
if (extension.i18n && extension.i18n.getMessage) {
getMessage = extension.i18n.getMessage
} else {
// fallback function
log.warn('browser.i18n API not available, calling back to english.')
const msg = require('../app/_locales/en/messages.json')
getMessage = function (key, substitutions) {
if (!msg[key]) {
log.error(key)
throw new Error(key)
}
let phrase = msg[key].message
if (substitutions && substitutions.length) {
phrase = phrase.replace(/\$1/g, substitutions[0])
if (substitutions.length > 1) {
phrase = phrase.replace(/\$2/g, substitutions[1])
}
}
return phrase
}
}
module.exports = getMessage
Loading…
Cancel
Save