aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.circleci/config.yml58
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG.md10
-rw-r--r--README.md1
-rw-r--r--app/_locales/en/messages.json605
-rw-r--r--app/_locales/fr/messages.json609
-rw-r--r--app/manifest.json7
-rw-r--r--app/scripts/background.js35
-rw-r--r--app/scripts/lib/local-store.js62
-rw-r--r--app/scripts/lib/tx-gas-utils.js6
-rw-r--r--development/genStates.js31
-rw-r--r--development/mock-dev.js (renamed from mock-dev.js)22
-rw-r--r--development/run-version-bump.js2
-rw-r--r--development/ui-dev.js (renamed from ui-dev.js)10
-rw-r--r--docs/translating-guide.md18
-rw-r--r--mascara/src/app/first-time/create-password-screen.js17
-rw-r--r--mascara/src/app/first-time/index.css128
-rw-r--r--old-ui/app/keychains/hd/restore-vault.js13
-rw-r--r--package-lock.json10
-rw-r--r--package.json14
-rw-r--r--test/base.conf.js4
-rw-r--r--test/integration/index.js2
-rw-r--r--test/integration/lib/add-token.js73
-rw-r--r--test/integration/lib/confirm-sig-requests.js44
-rw-r--r--test/integration/lib/first-time.js80
-rw-r--r--test/integration/lib/mascara-first-time.js94
-rw-r--r--test/integration/lib/send-new-ui.js129
-rw-r--r--test/lib/util.js53
-rw-r--r--test/unit/development/version–bump-test.js4
-rw-r--r--ui/app/accounts/import/index.js9
-rw-r--r--ui/app/accounts/import/json.js17
-rw-r--r--ui/app/accounts/import/private-key.js13
-rw-r--r--ui/app/accounts/import/seed.js6
-rw-r--r--ui/app/accounts/new-account/create-form.js13
-rw-r--r--ui/app/accounts/new-account/index.js9
-rw-r--r--ui/app/actions.js10
-rw-r--r--ui/app/app.js28
-rw-r--r--ui/app/components/account-dropdowns.js15
-rw-r--r--ui/app/components/account-export.js15
-rw-r--r--ui/app/components/account-menu/index.js17
-rw-r--r--ui/app/components/bn-as-decimal-input.js9
-rw-r--r--ui/app/components/buy-button-subview.js15
-rw-r--r--ui/app/components/coinbase-form.js5
-rw-r--r--ui/app/components/copyButton.js3
-rw-r--r--ui/app/components/copyable.js3
-rw-r--r--ui/app/components/customize-gas-modal/index.js25
-rw-r--r--ui/app/components/dropdowns/components/account-dropdowns.js22
-rw-r--r--ui/app/components/dropdowns/network-dropdown.js27
-rw-r--r--ui/app/components/dropdowns/token-menu-dropdown.js3
-rw-r--r--ui/app/components/ens-input.js5
-rw-r--r--ui/app/components/hex-as-decimal-input.js9
-rw-r--r--ui/app/components/modals/account-details-modal.js5
-rw-r--r--ui/app/components/modals/account-modal-container.js3
-rw-r--r--ui/app/components/modals/buy-options-modal.js15
-rw-r--r--ui/app/components/modals/deposit-ether-modal.js135
-rw-r--r--ui/app/components/modals/edit-account-name-modal.js7
-rw-r--r--ui/app/components/modals/export-private-key-modal.js16
-rw-r--r--ui/app/components/modals/hide-token-confirmation-modal.js13
-rw-r--r--ui/app/components/modals/modal.js21
-rw-r--r--ui/app/components/modals/new-account-modal.js17
-rw-r--r--ui/app/components/network.js25
-rw-r--r--ui/app/components/notice.js3
-rw-r--r--ui/app/components/pending-msg-details.js4
-rw-r--r--ui/app/components/pending-msg.js15
-rw-r--r--ui/app/components/pending-personal-msg-details.js4
-rw-r--r--ui/app/components/pending-tx/confirm-deploy-contract.js29
-rw-r--r--ui/app/components/pending-tx/confirm-send-ether.js21
-rw-r--r--ui/app/components/pending-tx/confirm-send-token.js35
-rw-r--r--ui/app/components/pending-typed-msg-details.js3
-rw-r--r--ui/app/components/pending-typed-msg.js11
-rw-r--r--ui/app/components/send-token/index.js35
-rw-r--r--ui/app/components/send/gas-fee-display-v2.js4
-rw-r--r--ui/app/components/send/gas-tooltip.js4
-rw-r--r--ui/app/components/send/to-autocomplete.js4
-rw-r--r--ui/app/components/shapeshift-form.js23
-rw-r--r--ui/app/components/shift-list-item.js15
-rw-r--r--ui/app/components/signature-request.js25
-rw-r--r--ui/app/components/token-list.js7
-rw-r--r--ui/app/components/transaction-list-item.js17
-rw-r--r--ui/app/components/transaction-list.js4
-rw-r--r--ui/app/components/tx-list-item.js3
-rw-r--r--ui/app/components/tx-list.js5
-rw-r--r--ui/app/components/tx-view.js21
-rw-r--r--ui/app/components/wallet-view.js9
-rw-r--r--ui/app/css/itcss/components/hero-balance.scss3
-rw-r--r--ui/app/css/itcss/components/index.scss3
-rw-r--r--ui/app/css/itcss/components/modal.scss50
-rw-r--r--ui/app/css/itcss/components/new-account.scss3
-rw-r--r--ui/app/css/itcss/components/newui-sections.scss23
-rw-r--r--ui/app/css/itcss/components/welcome-screen.scss59
-rw-r--r--ui/app/css/itcss/generic/index.scss37
-rw-r--r--ui/app/first-time/init-menu.js19
-rw-r--r--ui/app/keychains/hd/restore-vault.js13
-rw-r--r--ui/app/main-container.js14
-rw-r--r--ui/app/reducers/metamask.js6
-rw-r--r--ui/app/unlock.js144
-rw-r--r--ui/app/welcome-screen.js56
-rw-r--r--ui/i18n.js33
98 files changed, 2555 insertions, 886 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 9cd0a24ec..c14909783 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -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
diff --git a/.gitignore b/.gitignore
index 5f2d2f551..2bf38cad4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
npm-debug.log
node_modules
+yarn.lock
app/bower_components
test/bower_components
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 177e08bdd..e7d4a09fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/README.md b/README.md
index d45b73778..278357c23 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 8c28f1c43..1ca31427d 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -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"
}
}
diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json
new file mode 100644
index 000000000..7edbd41ab
--- /dev/null
+++ b/app/_locales/fr/messages.json
@@ -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"
+ }
+}
diff --git a/app/manifest.json b/app/manifest.json
index 0c89c2b3e..6fcf6cd7c 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -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/"
diff --git a/app/scripts/background.js b/app/scripts/background.js
index 601ae0372..ef5513ec7 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -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
//
diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js
new file mode 100644
index 000000000..5b47985f6
--- /dev/null
+++ b/app/scripts/lib/local-store.js
@@ -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
+}
diff --git a/app/scripts/lib/tx-gas-utils.js b/app/scripts/lib/tx-gas-utils.js
index 6f6ff7852..0fa9dd8d4 100644
--- a/app/scripts/lib/tx-gas-utils.js
+++ b/app/scripts/lib/tx-gas-utils.js
@@ -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
}
diff --git a/development/genStates.js b/development/genStates.js
index 39a672ee0..bc274c757 100644
--- a/development/genStates.js
+++ b/development/genStates.js
@@ -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)
+}
diff --git a/mock-dev.js b/development/mock-dev.js
index 6627c1153..a1fb3a86d 100644
--- a/mock-dev.js
+++ b/development/mock-dev.js
@@ -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')
//
diff --git a/development/run-version-bump.js b/development/run-version-bump.js
index fde14566e..98757f58e 100644
--- a/development/run-version-bump.js
+++ b/development/run-version-bump.js
@@ -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)
diff --git a/ui-dev.js b/development/ui-dev.js
index 620d81667..cac433909 100644
--- a/ui-dev.js
+++ b/development/ui-dev.js
@@ -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')
diff --git a/docs/translating-guide.md b/docs/translating-guide.md
new file mode 100644
index 000000000..62d444b5a
--- /dev/null
+++ b/docs/translating-guide.md
@@ -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.
+
diff --git a/mascara/src/app/first-time/create-password-screen.js b/mascara/src/app/first-time/create-password-screen.js
index 450d6a479..192da3399 100644
--- a/mascara/src/app/first-time/create-password-screen.js
+++ b/mascara/src/app/first-time/create-password-screen.js
@@ -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)),
})
diff --git a/mascara/src/app/first-time/index.css b/mascara/src/app/first-time/index.css
index f59eb4ce1..6c45816bd 100644
--- a/mascara/src/app/first-time/index.css
+++ b/mascara/src/app/first-time/index.css
@@ -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;
}
diff --git a/old-ui/app/keychains/hd/restore-vault.js b/old-ui/app/keychains/hd/restore-vault.js
index 222172dfd..d334d8e5f 100644
--- a/old-ui/app/keychains/hd/restore-vault.js
+++ b/old-ui/app/keychains/hd/restore-vault.js
@@ -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))
diff --git a/package-lock.json b/package-lock.json
index 535a0f1eb..200598283 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index d4c6e4260..5faf871a5 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/test/base.conf.js b/test/base.conf.js
index 82b9d8eec..adb5357e8 100644
--- a/test/base.conf.js
+++ b/test/base.conf.js
@@ -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
diff --git a/test/integration/index.js b/test/integration/index.js
index 144303dbb..b266ddf37 100644
--- a/test/integration/index.js
+++ b/test/integration/index.js
@@ -23,4 +23,4 @@ pump(
console.log(`Integration test build completed: "${bundlePath}"`)
process.exit(0)
}
-) \ No newline at end of file
+)
diff --git a/test/integration/lib/add-token.js b/test/integration/lib/add-token.js
index dd4251cc4..42ed28dca 100644
--- a/test/integration/lib/add-token.js
+++ b/test/integration/lib/add-token.js
@@ -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)
- })
-}
diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js
index e49424c37..9737a2283 100644
--- a/test/integration/lib/confirm-sig-requests.js
+++ b/test/integration/lib/confirm-sig-requests.js
@@ -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)
- })
-} \ No newline at end of file
diff --git a/test/integration/lib/first-time.js b/test/integration/lib/first-time.js
index 764eae47c..052d89518 100644
--- a/test/integration/lib/first-time.js
+++ b/test/integration/lib/first-time.js
@@ -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)
- })
-} \ No newline at end of file
diff --git a/test/integration/lib/mascara-first-time.js b/test/integration/lib/mascara-first-time.js
index 515c7f383..bcbc94ff6 100644
--- a/test/integration/lib/mascara-first-time.js
+++ b/test/integration/lib/mascara-first-time.js
@@ -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
}
diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js
index 3456f2367..faab10fdf 100644
--- a/test/integration/lib/send-new-ui.js
+++ b/test/integration/lib/send-new-ui.js
@@ -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)
- })
-} \ No newline at end of file
diff --git a/test/lib/util.js b/test/lib/util.js
new file mode 100644
index 000000000..626280745
--- /dev/null
+++ b/test/lib/util.js
@@ -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
+}
diff --git a/test/unit/development/version–bump-test.js b/test/unit/development/version–bump-test.js
index 1c445c8b4..5e37d4410 100644
--- a/test/unit/development/version–bump-test.js
+++ b/test/unit/development/version–bump-test.js
@@ -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))
})
})
-
-
diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js
index adb52db74..c1b190e3d 100644
--- a/ui/app/accounts/import/index.js
+++ b/ui/app/accounts/import/index.js
@@ -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)
diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js
index c13622166..1b5e485d7 100644
--- a/ui/app/accounts/import/json.js
+++ b/ui/app/accounts/import/json.js
@@ -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,10 +99,10 @@ 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)
}
-
+
this.props.importNewJsonAccount([ fileContents, password ])
}
}
diff --git a/ui/app/accounts/import/private-key.js b/ui/app/accounts/import/private-key.js
index 12f3a6430..bc9e9384e 100644
--- a/ui/app/accounts/import/private-key.js
+++ b/ui/app/accounts/import/private-key.js
@@ -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'),
]),
]),
diff --git a/ui/app/accounts/import/seed.js b/ui/app/accounts/import/seed.js
index b4a7c0afa..9ffc669a2 100644
--- a/ui/app/accounts/import/seed.js
+++ b/ui/app/accounts/import/seed.js
@@ -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')),
])
)
}
-
diff --git a/ui/app/accounts/new-account/create-form.js b/ui/app/accounts/new-account/create-form.js
index a6b3bba4b..8ef842a2a 100644
--- a/ui/app/accounts/new-account/create-form.js
+++ b/ui/app/accounts/new-account/create-form.js
@@ -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'),
]),
]),
diff --git a/ui/app/accounts/new-account/index.js b/ui/app/accounts/new-account/index.js
index acf0dc6e4..854568c77 100644
--- a/ui/app/accounts/new-account/index.js
+++ b/ui/app/accounts/new-account/index.js
@@ -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')
@@ -42,10 +43,10 @@ AccountDetailsModal.prototype.render = function () {
const { displayedForm, displayForm } = this.props
return h('div.new-account', {}, [
-
+
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')),
]),
diff --git a/ui/app/actions.js b/ui/app/actions.js
index b56265edc..092af080b 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -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,
diff --git a/ui/app/app.js b/ui/app/app.js
index 4e6da24c3..9708a2485 100644
--- a/ui/app/app.js
+++ b/ui/app/app.js
@@ -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)
}
diff --git a/ui/app/components/account-dropdowns.js b/ui/app/components/account-dropdowns.js
index 1cd7a0847..1612d7b6a 100644
--- a/ui/app/components/account-dropdowns.js
+++ b/ui/app/components/account-dropdowns.js
@@ -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'),
),
]
)
diff --git a/ui/app/components/account-export.js b/ui/app/components/account-export.js
index 32b103c86..5637bc8d0 100644
--- a/ui/app/components/account-export.js
+++ b/ui/app/components/account-export.js
@@ -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')),
])
}
}
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
index 1a0103d4f..e838e8916 100644
--- a/ui/app/components/account-menu/index.js
+++ b/ui/app/components/account-menu/index.js
@@ -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 }
}
diff --git a/ui/app/components/bn-as-decimal-input.js b/ui/app/components/bn-as-decimal-input.js
index 22e37602e..70701b039 100644
--- a/ui/app/components/bn-as-decimal-input.js
+++ b/ui/app/components/bn-as-decimal-input.js
@@ -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
diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js
index d5958787b..1e277a94b 100644
--- a/ui/app/components/buy-button-subview.js
+++ b/ui/app/components/buy-button-subview.js
@@ -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),
}),
diff --git a/ui/app/components/coinbase-form.js b/ui/app/components/coinbase-form.js
index f70208625..e442b43d5 100644
--- a/ui/app/components/coinbase-form.js
+++ b/ui/app/components/coinbase-form.js
@@ -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')),
]),
])
}
diff --git a/ui/app/components/copyButton.js b/ui/app/components/copyButton.js
index a25d0719c..355f78d45 100644
--- a/ui/app/components/copyButton.js
+++ b/ui/app/components/copyButton.js
@@ -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: {
diff --git a/ui/app/components/copyable.js b/ui/app/components/copyable.js
index a4f6f4bc6..fca7d3863 100644
--- a/ui/app/components/copyable.js
+++ b/ui/app/components/copyable.js
@@ -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: {
diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js
index 826d2cd4b..920dfeab6 100644
--- a/ui/app/components/customize-gas-modal/index.js
+++ b/ui/app/components/customize-gas-modal/index.js
@@ -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')]),
]),
]),
diff --git a/ui/app/components/dropdowns/components/account-dropdowns.js b/ui/app/components/dropdowns/components/account-dropdowns.js
index 40918cc53..e5359c1d6 100644
--- a/ui/app/components/dropdowns/components/account-dropdowns.js
+++ b/ui/app/components/dropdowns/components/account-dropdowns.js
@@ -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)
-
diff --git a/ui/app/components/dropdowns/network-dropdown.js b/ui/app/components/dropdowns/network-dropdown.js
index ff10c0758..5afe730c1 100644
--- a/ui/app/components/dropdowns/network-dropdown.js
+++ b/ui/app/components/dropdowns/network-dropdown.js
@@ -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
diff --git a/ui/app/components/dropdowns/token-menu-dropdown.js b/ui/app/components/dropdowns/token-menu-dropdown.js
index dc7a985e3..a4f93b505 100644
--- a/ui/app/components/dropdowns/token-menu-dropdown.js
+++ b/ui/app/components/dropdowns/token-menu-dropdown.js
@@ -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')),
]),
]),
diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js
index 6553053f7..add67ea35 100644
--- a/ui/app/components/ens-input.js
+++ b/ui/app/components/ens-input.js
@@ -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,
})
}
diff --git a/ui/app/components/hex-as-decimal-input.js b/ui/app/components/hex-as-decimal-input.js
index 4a71e9585..a43d44f89 100644
--- a/ui/app/components/hex-as-decimal-input.js
+++ b/ui/app/components/hex-as-decimal-input.js
@@ -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
diff --git a/ui/app/components/modals/account-details-modal.js b/ui/app/components/modals/account-details-modal.js
index c1f7a3236..75f989e86 100644
--- a/ui/app/components/modals/account-details-modal.js
+++ b/ui/app/components/modals/account-details-modal.js
@@ -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')),
])
}
diff --git a/ui/app/components/modals/account-modal-container.js b/ui/app/components/modals/account-modal-container.js
index c548fb7b3..08540aa76 100644
--- a/ui/app/components/modals/account-modal-container.js
+++ b/ui/app/components/modals/account-modal-container.js
@@ -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')),
]),
diff --git a/ui/app/components/modals/buy-options-modal.js b/ui/app/components/modals/buy-options-modal.js
index 74a7a847e..7eb73c3a6 100644
--- a/ui/app/components/modals/buy-options-modal.js
+++ b/ui/app/components/modals/buy-options-modal.js
@@ -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'))),
]),
])
}
diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js
index 7547dbcf5..26ff3ea03 100644
--- a/ui/app/components/modals/deposit-ether-modal.js
+++ b/ui/app/components/modals/deposit-ether-modal.js
@@ -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()
@@ -128,55 +128,64 @@ 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: t('viewAccount'),
+ onButtonClick: () => this.goToAccountDetailsModal(),
+ hide: buyingWithShapeshift,
+ }),
- 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',
- onButtonClick: () => this.goToAccountDetailsModal(),
- hide: buyingWithShapeshift,
- }),
-
- this.renderRow({
- logo: h('i.fa.fa-tint.fa-2x'),
- title: FAUCET_ROW_TITLE,
- text: facuetRowText(networkName),
- buttonLabel: 'Get Ether',
- onButtonClick: () => toFaucet(network),
- hide: !isTestNetwork || buyingWithShapeshift,
- }),
-
- this.renderRow({
- logo: h('img.deposit-ether-modal__buy-row__coinbase-logo', {
- src: '../../../images/coinbase logo.png',
+ this.renderRow({
+ logo: h('i.fa.fa-tint.fa-2x'),
+ title: FAUCET_ROW_TITLE,
+ text: facuetRowText(networkName),
+ buttonLabel: t('getEther'),
+ onButtonClick: () => toFaucet(network),
+ hide: !isTestNetwork || buyingWithShapeshift,
}),
- title: COINBASE_ROW_TITLE,
- text: COINBASE_ROW_TEXT,
- buttonLabel: 'Continue to Coinbase',
- onButtonClick: () => toCoinbase(address),
- hide: isTestNetwork || buyingWithShapeshift,
- }),
- this.renderRow({
- logo: h('img.deposit-ether-modal__buy-row__shapeshift-logo', {
- src: '../../../images/shapeshift logo.png',
+ this.renderRow({
+ 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: t('continueToCoinbase'),
+ onButtonClick: () => toCoinbase(address),
+ hide: isTestNetwork || buyingWithShapeshift,
}),
- title: SHAPESHIFT_ROW_TITLE,
- text: SHAPESHIFT_ROW_TEXT,
- buttonLabel: 'Buy with Shapeshift',
- onButtonClick: () => this.setState({ buyingWithShapeshift: true }),
- hide: isTestNetwork,
- hideButton: buyingWithShapeshift,
- hideTitle: buyingWithShapeshift,
- onBackClick: () => this.setState({ buyingWithShapeshift: false }),
- showBackButton: this.state.buyingWithShapeshift,
- className: buyingWithShapeshift && 'deposit-ether-modal__buy-row__shapeshift-buy',
- }),
+
+ this.renderRow({
+ logo: h('div.deposit-ether-modal__logo', {
+ style: {
+ backgroundImage: 'url(\'../../../images/shapeshift logo.png\')',
+ },
+ }),
+ title: SHAPESHIFT_ROW_TITLE,
+ text: SHAPESHIFT_ROW_TEXT,
+ buttonLabel: t('shapeshiftBuy'),
+ onButtonClick: () => this.setState({ buyingWithShapeshift: true }),
+ hide: isTestNetwork,
+ hideButton: buyingWithShapeshift,
+ hideTitle: buyingWithShapeshift,
+ onBackClick: () => this.setState({ buyingWithShapeshift: false }),
+ showBackButton: this.state.buyingWithShapeshift,
+ className: buyingWithShapeshift && 'deposit-ether-modal__buy-row__shapeshift-buy',
+ }),
+
+ buyingWithShapeshift && h(ShapeshiftForm),
- buyingWithShapeshift && h(ShapeshiftForm),
+ ]),
]),
])
diff --git a/ui/app/components/modals/edit-account-name-modal.js b/ui/app/components/modals/edit-account-name-modal.js
index e2361140d..6efa8d476 100644
--- a/ui/app/components/modals/edit-account-name-modal.js
+++ b/ui/app/components/modals/edit-account-name-modal.js
@@ -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'),
]),
]),
diff --git a/ui/app/components/modals/export-private-key-modal.js b/ui/app/components/modals/export-private-key-modal.js
index 422f23f44..017177cfd 100644
--- a/ui/app/components/modals/export-private-key-modal.js
+++ b/ui/app/components/modals/export-private-key-modal.js
@@ -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),
diff --git a/ui/app/components/modals/hide-token-confirmation-modal.js b/ui/app/components/modals/hide-token-confirmation-modal.js
index 56c7ba299..33d8062c6 100644
--- a/ui/app/components/modals/hide-token-confirmation-modal.js
+++ b/ui/app/components/modals/hide-token-confirmation-modal.js
@@ -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'),
]),
]),
]),
diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js
index 8e9e58985..501b83430 100644
--- a/ui/app/components/modals/modal.js
+++ b/ui/app/components/modals/modal.js
@@ -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: {
diff --git a/ui/app/components/modals/new-account-modal.js b/ui/app/components/modals/new-account-modal.js
index fc1fd413d..298b76af4 100644
--- a/ui/app/components/modals/new-account-modal.js
+++ b/ui/app/components/modals/new-account-modal.js
@@ -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'),
]),
]),
]),
diff --git a/ui/app/components/network.js b/ui/app/components/network.js
index 3e91fa807..f3df2242a 100644
--- a/ui/app/components/network.js
+++ b/ui/app/components/network.js
@@ -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'),
])
}
diff --git a/ui/app/components/notice.js b/ui/app/components/notice.js
index 9d2e89cb0..8b0ce1e8b 100644
--- a/ui/app/components/notice.js
+++ b/ui/app/components/notice.js
@@ -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')),
])
)
}
diff --git a/ui/app/components/pending-msg-details.js b/ui/app/components/pending-msg-details.js
index 718a22de0..87e66855d 100644
--- a/ui/app/components/pending-msg-details.js
+++ b/ui/app/components/pending-msg-details.js
@@ -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 () {
])
)
}
-
diff --git a/ui/app/components/pending-msg.js b/ui/app/components/pending-msg.js
index 834719c53..dc406b955 100644
--- a/ui/app/components/pending-msg.js
+++ b/ui/app/components/pending-msg.js
@@ -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')),
]),
])
)
}
-
diff --git a/ui/app/components/pending-personal-msg-details.js b/ui/app/components/pending-personal-msg-details.js
index 1050513f2..b896e9a7e 100644
--- a/ui/app/components/pending-personal-msg-details.js
+++ b/ui/app/components/pending-personal-msg-details.js
@@ -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 () {
])
)
}
-
diff --git a/ui/app/components/pending-tx/confirm-deploy-contract.js b/ui/app/components/pending-tx/confirm-deploy-contract.js
index ae6c6ef7b..49fbe6387 100644
--- a/ui/app/components/pending-tx/confirm-deploy-contract.js
+++ b/ui/app/components/pending-tx/confirm-deploy-contract.js
@@ -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')]),
]),
])
diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js
index 3f8d9c28f..4d4732bdb 100644
--- a/ui/app/components/pending-tx/confirm-send-ether.js
+++ b/ui/app/components/pending-tx/confirm-send-ether.js
@@ -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 })
}
}
diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js
index dd2bef3da..69afa8094 100644
--- a/ui/app/components/pending-tx/confirm-send-token.js
+++ b/ui/app/components/pending-tx/confirm-send-token.js
@@ -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 })
}
}
diff --git a/ui/app/components/pending-typed-msg-details.js b/ui/app/components/pending-typed-msg-details.js
index b5fd29f71..ae0a1171e 100644
--- a/ui/app/components/pending-typed-msg-details.js
+++ b/ui/app/components/pending-typed-msg-details.js
@@ -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: {
diff --git a/ui/app/components/pending-typed-msg.js b/ui/app/components/pending-typed-msg.js
index f8926d0a3..ccde5e8af 100644
--- a/ui/app/components/pending-typed-msg.js
+++ b/ui/app/components/pending-typed-msg.js
@@ -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')),
]),
])
diff --git a/ui/app/components/send-token/index.js b/ui/app/components/send-token/index.js
index 99d078251..58743b641 100644
--- a/ui/app/components/send-token/index.js
+++ b/ui/app/components/send-token/index.js
@@ -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 }),
]),
diff --git a/ui/app/components/send/gas-fee-display-v2.js b/ui/app/components/send/gas-fee-display-v2.js
index 0c4c3f7a9..0c6f76303 100644
--- a/ui/app/components/send/gas-fee-display-v2.js
+++ b/ui/app/components/send/gas-fee-display-v2.js
@@ -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 () {
])
}
-
diff --git a/ui/app/components/send/gas-tooltip.js b/ui/app/components/send/gas-tooltip.js
index 46aff3499..d925d3ed8 100644
--- a/ui/app/components/send/gas-tooltip.js
+++ b/ui/app/components/send/gas-tooltip.js
@@ -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 () {
]),
])
}
-
diff --git a/ui/app/components/send/to-autocomplete.js b/ui/app/components/send/to-autocomplete.js
index e0cdd0a58..72074229e 100644
--- a/ui/app/components/send/to-autocomplete.js
+++ b/ui/app/components/send/to-autocomplete.js
@@ -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 () {
])
}
-
diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js
index 87eb1588a..3f8c17932 100644
--- a/ui/app/components/shapeshift-form.js
+++ b/ui/app/components/shapeshift-form.js
@@ -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 {
@@ -15,7 +16,7 @@ function mapStateToProps (state) {
selectedAddress,
} = state.metamask
const { warning } = state.appState
-
+
return {
coinOptions,
tokenExchangeRates,
@@ -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')]),
])
}
diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js
index 017bf9f0c..fddbc6821 100644
--- a/ui/app/components/shift-list-item.js
+++ b/ui/app/components/shift-list-item.js
@@ -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 ''
}
diff --git a/ui/app/components/signature-request.js b/ui/app/components/signature-request.js
index c5cc23aa8..7bf34e7b6 100644
--- a/ui/app/components/signature-request.js
+++ b/ui/app/components/signature-request.js
@@ -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 () {
)
}
-
diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js
index 8e06e0f27..01529aeda 100644
--- a/ui/app/components/token-list.js
+++ b/ui/app/components/token-list.js
@@ -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')),
])
}
diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js
index a45cd441a..6d6e79bd5 100644
--- a/ui/app/components/transaction-list-item.js
+++ b/ui/app/components/transaction-list-item.js
@@ -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') + `)`),
])
}
}
diff --git a/ui/app/components/transaction-list.js b/ui/app/components/transaction-list.js
index 69b72614c..07f7a06ae 100644
--- a/ui/app/components/transaction-list.js
+++ b/ui/app/components/transaction-list.js
@@ -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')),
]),
]),
])
)
}
-
diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js
index 1a13070c8..849d70489 100644
--- a/ui/app/components/tx-list-item.js
+++ b/ui/app/components/tx-list-item.js
@@ -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')
}
}
diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js
index 1729e6a6f..34dc837ae 100644
--- a/ui/app/components/tx-list.js
+++ b/ui/app/components/tx-list.js
@@ -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)
}
diff --git a/ui/app/components/tx-view.js b/ui/app/components/tx-view.js
index b25d8e0f9..96d776270 100644
--- a/ui/app/components/tx-view.js
+++ b/ui/app/components/tx-view.js
@@ -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: {
diff --git a/ui/app/components/wallet-view.js b/ui/app/components/wallet-view.js
index 34f27ca2a..18452205c 100644
--- a/ui/app/components/wallet-view.js
+++ b/ui/app/components/wallet-view.js
@@ -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')),
])
}
diff --git a/ui/app/css/itcss/components/hero-balance.scss b/ui/app/css/itcss/components/hero-balance.scss
index 4af0c2c55..a3f051361 100644
--- a/ui/app/css/itcss/components/hero-balance.scss
+++ b/ui/app/css/itcss/components/hero-balance.scss
@@ -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;
}
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
index 0219f9fb2..f107b7aca 100644
--- a/ui/app/css/itcss/components/index.scss
+++ b/ui/app/css/itcss/components/index.scss
@@ -55,3 +55,6 @@
@import './new-account.scss';
@import './tooltip.scss';
+
+@import './welcome-screen.scss';
+
diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss
index 53e3bff00..6c4106f8b 100644
--- a/ui/app/css/itcss/components/modal.scss
+++ b/ui/app/css/itcss/components/modal.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 {
diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss
index 08cff867d..c2cefe4ad 100644
--- a/ui/app/css/itcss/components/new-account.scss
+++ b/ui/app/css/itcss/components/new-account.scss
@@ -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 {
diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss
index ecf5e1036..5cdda5e6c 100644
--- a/ui/app/css/itcss/components/newui-sections.scss
+++ b/ui/app/css/itcss/components/newui-sections.scss
@@ -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;
@@ -313,4 +313,23 @@ $wallet-view-bg: $alabaster;
@media screen and (min-width: 1281px) {
width: 62vw;
}
-} \ No newline at end of file
+}
+
+.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;
+}
diff --git a/ui/app/css/itcss/components/welcome-screen.scss b/ui/app/css/itcss/components/welcome-screen.scss
new file mode 100644
index 000000000..bfd174ad9
--- /dev/null
+++ b/ui/app/css/itcss/components/welcome-screen.scss
@@ -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);
+ }
+}
diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss
index 9b3d7475b..0077cb661 100644
--- a/ui/app/css/itcss/generic/index.scss
+++ b/ui/app/css/itcss/generic/index.scss
@@ -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;
}
}
diff --git a/ui/app/first-time/init-menu.js b/ui/app/first-time/init-menu.js
index 0e08da8db..370fdd5b7 100644
--- a/ui/app/first-time/init-menu.js
+++ b/ui/app/first-time/init-menu.js
@@ -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
}
diff --git a/ui/app/keychains/hd/restore-vault.js b/ui/app/keychains/hd/restore-vault.js
index d1761f17d..cb4088f61 100644
--- a/ui/app/keychains/hd/restore-vault.js
+++ b/ui/app/keychains/hd/restore-vault.js
@@ -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))
diff --git a/ui/app/main-container.js b/ui/app/main-container.js
index 292abcc3d..eed4bd164 100644
--- a/ui/app/main-container.js
+++ b/ui/app/main-container.js
@@ -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' }))
}
}
diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js
index 029d830ec..4ca7d221e 100644
--- a/ui/app/reducers/metamask.js
+++ b/ui/app/reducers/metamask.js
@@ -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
diff --git a/ui/app/unlock.js b/ui/app/unlock.js
index cafe3a859..ac97d04d0 100644
--- a/ui/app/unlock.js
+++ b/ui/app/unlock.js
@@ -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,83 +29,72 @@ 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(Mascot, {
- animationEventEmitter: this.animationEventEmitter,
- }),
-
- h('h1', {
- style: {
- fontSize: '1.4em',
- textTransform: 'uppercase',
- color: '#7F8082',
- },
- }, 'MetaMask'),
-
- h('input.large-input', {
- type: 'password',
- id: 'password-box',
- placeholder: 'enter password',
- style: {
- background: 'white',
- },
- onKeyPress: this.onKeyPress.bind(this),
- onInput: this.inputChanged.bind(this),
- }),
-
- h('.error', {
- style: {
- display: warning ? 'block' : 'none',
- padding: '0 20px',
- textAlign: 'center',
- },
- }, warning),
-
- h('button.primary.cursor-pointer', {
- onClick: this.onSubmit.bind(this),
- style: {
- margin: 10,
- },
- }, 'Log In'),
- ]),
-
- h('.flex-row.flex-center.flex-grow', [
- h('p.pointer', {
- onClick: () => {
- this.props.dispatch(actions.markPasswordForgotten())
- if (environmentType() === 'popup') {
- global.platform.openExtensionInBrowser()
- }
- },
- style: {
- fontSize: '0.8em',
- color: 'rgb(247, 134, 28)',
- 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'))
- .then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
- },
- style: {
- fontSize: '0.8em',
- color: '#aeaeae',
- textDecoration: 'underline',
- marginTop: '32px',
- },
- }, 'Use classic interface'),
- ]),
-
+ h('.unlock-screen', [
+
+ h(Mascot, {
+ animationEventEmitter: this.animationEventEmitter,
+ }),
+
+ h('h1', {
+ style: {
+ fontSize: '1.4em',
+ textTransform: 'uppercase',
+ color: '#7F8082',
+ },
+ }, t('appName')),
+
+ h('input.large-input', {
+ type: 'password',
+ id: 'password-box',
+ placeholder: 'enter password',
+ style: {
+ background: 'white',
+ },
+ onKeyPress: this.onKeyPress.bind(this),
+ onInput: this.inputChanged.bind(this),
+ }),
+
+ h('.error', {
+ style: {
+ display: warning ? 'block' : 'none',
+ padding: '0 20px',
+ textAlign: 'center',
+ },
+ }, warning),
+
+ h('button.primary.cursor-pointer', {
+ onClick: this.onSubmit.bind(this),
+ style: {
+ margin: 10,
+ },
+ }, 'Log In'),
+
+ h('p.pointer', {
+ onClick: () => {
+ this.props.dispatch(actions.markPasswordForgotten())
+ if (environmentType() === 'popup') {
+ global.platform.openExtensionInBrowser()
+ }
+ },
+ style: {
+ fontSize: '0.8em',
+ color: 'rgb(247, 134, 28)',
+ textDecoration: 'underline',
+ },
+ }, 'Restore from seed phrase'),
+
+ h('p.pointer', {
+ onClick: () => {
+ this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
+ .then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
+ },
+ style: {
+ fontSize: '0.8em',
+ color: '#aeaeae',
+ textDecoration: 'underline',
+ marginTop: '32px',
+ },
+ }, 'Use classic interface'),
])
)
}
diff --git a/ui/app/welcome-screen.js b/ui/app/welcome-screen.js
new file mode 100644
index 000000000..cdbb6dba8
--- /dev/null
+++ b/ui/app/welcome-screen.js
@@ -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)
diff --git a/ui/i18n.js b/ui/i18n.js
new file mode 100644
index 000000000..abfece426
--- /dev/null
+++ b/ui/i18n.js
@@ -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