feature/default_network_editable
commit
e48934dc04
@ -0,0 +1,202 @@ |
||||
version: 2 |
||||
|
||||
workflows: |
||||
version: 2 |
||||
full_test: |
||||
jobs: |
||||
- prep-deps-npm |
||||
- prep-deps-firefox |
||||
- prep-scss: |
||||
requires: |
||||
- prep-deps-npm |
||||
- test-lint: |
||||
requires: |
||||
- prep-deps-npm |
||||
- test-unit: |
||||
requires: |
||||
- prep-deps-npm |
||||
- 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-chrome: |
||||
requires: |
||||
- prep-deps-npm |
||||
- prep-scss |
||||
- test-integration-flat-firefox: |
||||
requires: |
||||
- prep-deps-npm |
||||
- prep-deps-firefox |
||||
- prep-scss |
||||
|
||||
jobs: |
||||
prep-deps-npm: |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- restore_cache: |
||||
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||
- run: |
||||
name: Install deps via npm |
||||
command: npm install |
||||
- save_cache: |
||||
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||
paths: |
||||
- node_modules |
||||
|
||||
prep-deps-firefox: |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- run: |
||||
name: Download Firefox |
||||
command: > |
||||
wget https://ftp.mozilla.org/pub/firefox/releases/58.0/linux-x86_64/en-US/firefox-58.0.tar.bz2 |
||||
&& tar xjf firefox-58.0.tar.bz2 |
||||
- save_cache: |
||||
key: dependency-cache-firefox-{{ .Revision }} |
||||
paths: |
||||
- firefox |
||||
|
||||
|
||||
prep-scss: |
||||
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 |
||||
- run: |
||||
name: Build for integration tests |
||||
command: npm run test:integration:build |
||||
- save_cache: |
||||
key: scss-cache-{{ checksum "scss_checksum" }} |
||||
paths: |
||||
- ui/app/css/output |
||||
|
||||
test-lint: |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- restore_cache: |
||||
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||
- run: |
||||
name: Test |
||||
command: npm run lint |
||||
|
||||
test-unit: |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- restore_cache: |
||||
key: dependency-cache-{{ checksum "package-lock.json" }} |
||||
- run: |
||||
name: test:coverage |
||||
command: npm run test:coverage |
||||
|
||||
test-integration-flat-firefox: |
||||
environment: |
||||
browsers: '["Firefox"]' |
||||
docker: |
||||
- image: circleci/node:8-browsers |
||||
steps: |
||||
- checkout |
||||
- restore_cache: |
||||
key: dependency-cache-firefox-{{ .Revision }} |
||||
- run: |
||||
name: Install firefox |
||||
command: > |
||||
sudo rm -r /opt/firefox |
||||
&& sudo mv firefox /opt/firefox58 |
||||
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old |
||||
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox |
||||
- 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-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: |
||||
- checkout |
||||
- restore_cache: |
||||
key: dependency-cache-firefox-{{ .Revision }} |
||||
- run: |
||||
name: Install firefox |
||||
command: > |
||||
sudo rm -r /opt/firefox |
||||
&& sudo mv firefox /opt/firefox58 |
||||
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old |
||||
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox |
||||
- 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 |
||||
|
||||
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 |
@ -0,0 +1,3 @@ |
||||
{ |
||||
"exceptions": ["https://nodesecurity.io/advisories/566"] |
||||
} |
@ -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" |
||||
} |
||||
} |
||||
|
@ -1,17 +0,0 @@ |
||||
machine: |
||||
node: |
||||
version: 8.1.4 |
||||
test: |
||||
override: |
||||
- "npm test" |
||||
dependencies: |
||||
pre: |
||||
- sudo apt-get update |
||||
# get latest stable firefox |
||||
- sudo apt-get install firefox |
||||
- firefox_cmd=`which firefox`; sudo rm -f $firefox_cmd; sudo ln -s `which firefox.ubuntu` $firefox_cmd |
||||
# get latest stable chrome |
||||
- wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - |
||||
- sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' |
||||
- sudo apt-get update |
||||
- sudo apt-get install google-chrome-stable |
@ -1,18 +1,21 @@ |
||||
const fs = require('fs') |
||||
const path = require('path') |
||||
const { promisify } = require('util') |
||||
|
||||
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) |
||||
} |
||||
|
@ -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. |
||||
|
@ -1,100 +1,122 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const h = require('react-hyperscript') |
||||
const connect = require('react-redux').connect |
||||
const actions = require('../../../../ui/app/actions') |
||||
const FileInput = require('react-simple-file-input').default |
||||
const PropTypes = require('prop-types') |
||||
|
||||
const HELP_LINK = 'https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file' |
||||
|
||||
module.exports = connect(mapStateToProps)(JsonImportSubview) |
||||
class JsonImportSubview extends Component { |
||||
constructor (props) { |
||||
super(props) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
error: state.appState.warning, |
||||
this.state = { |
||||
file: null, |
||||
fileContents: '', |
||||
} |
||||
} |
||||
} |
||||
|
||||
inherits(JsonImportSubview, Component) |
||||
function JsonImportSubview () { |
||||
Component.call(this) |
||||
} |
||||
render () { |
||||
const { error } = this.props |
||||
|
||||
JsonImportSubview.prototype.render = function () { |
||||
const { error } = this.props |
||||
|
||||
return ( |
||||
h('div', { |
||||
style: { |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
alignItems: 'center', |
||||
padding: '5px 15px 0px 15px', |
||||
}, |
||||
}, [ |
||||
|
||||
h('p', 'Used by a variety of different clients'), |
||||
h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), |
||||
|
||||
h(FileInput, { |
||||
readAs: 'text', |
||||
onLoad: this.onLoad.bind(this), |
||||
return ( |
||||
h('div', { |
||||
style: { |
||||
margin: '20px 0px 12px 20px', |
||||
fontSize: '15px', |
||||
display: 'flex', |
||||
flexDirection: 'column', |
||||
alignItems: 'center', |
||||
padding: '5px 15px 0px 15px', |
||||
}, |
||||
}), |
||||
}, [ |
||||
|
||||
h('p', 'Used by a variety of different clients'), |
||||
h('a.warning', { |
||||
href: HELP_LINK, |
||||
target: '_blank', |
||||
}, 'File import not working? Click here!'), |
||||
|
||||
h(FileInput, { |
||||
readAs: 'text', |
||||
onLoad: this.onLoad.bind(this), |
||||
style: { |
||||
margin: '20px 0px 12px 20px', |
||||
fontSize: '15px', |
||||
}, |
||||
}), |
||||
|
||||
h('input.large-input.letter-spacey', { |
||||
type: 'password', |
||||
placeholder: 'Enter password', |
||||
id: 'json-password-box', |
||||
onKeyPress: this.createKeyringOnEnter.bind(this), |
||||
style: { |
||||
width: 260, |
||||
marginTop: 12, |
||||
}, |
||||
}), |
||||
|
||||
h('button.primary', { |
||||
onClick: this.createNewKeychain.bind(this), |
||||
style: { |
||||
margin: 12, |
||||
}, |
||||
}, 'Import'), |
||||
|
||||
error ? h('span.error', error) : null, |
||||
]) |
||||
) |
||||
} |
||||
|
||||
h('input.large-input.letter-spacey', { |
||||
type: 'password', |
||||
placeholder: 'Enter password', |
||||
id: 'json-password-box', |
||||
onKeyPress: this.createKeyringOnEnter.bind(this), |
||||
style: { |
||||
width: 260, |
||||
marginTop: 12, |
||||
}, |
||||
}), |
||||
onLoad (event, file) { |
||||
this.setState({file: file, fileContents: event.target.result}) |
||||
} |
||||
|
||||
h('button.primary', { |
||||
onClick: this.createNewKeychain.bind(this), |
||||
style: { |
||||
margin: 12, |
||||
}, |
||||
}, 'Import'), |
||||
createKeyringOnEnter (event) { |
||||
if (event.key === 'Enter') { |
||||
event.preventDefault() |
||||
this.createNewKeychain() |
||||
} |
||||
} |
||||
|
||||
error ? h('span.error', error) : null, |
||||
]) |
||||
) |
||||
} |
||||
createNewKeychain () { |
||||
const { fileContents } = this.state |
||||
|
||||
JsonImportSubview.prototype.onLoad = function (event, file) { |
||||
this.setState({file: file, fileContents: event.target.result}) |
||||
} |
||||
if (!fileContents) { |
||||
const message = 'You must select a file to import.' |
||||
return this.props.displayWarning(message) |
||||
} |
||||
|
||||
JsonImportSubview.prototype.createKeyringOnEnter = function (event) { |
||||
if (event.key === 'Enter') { |
||||
event.preventDefault() |
||||
this.createNewKeychain() |
||||
} |
||||
} |
||||
const passwordInput = document.getElementById('json-password-box') |
||||
const password = passwordInput.value |
||||
|
||||
JsonImportSubview.prototype.createNewKeychain = function () { |
||||
const state = this.state |
||||
const { fileContents } = state |
||||
if (!password) { |
||||
const message = 'You must enter a password for the selected file.' |
||||
return this.props.displayWarning(message) |
||||
} |
||||
|
||||
if (!fileContents) { |
||||
const message = 'You must select a file to import.' |
||||
return this.props.dispatch(actions.displayWarning(message)) |
||||
this.props.importNewAccount([ fileContents, password ]) |
||||
} |
||||
} |
||||
|
||||
const passwordInput = document.getElementById('json-password-box') |
||||
const password = passwordInput.value |
||||
JsonImportSubview.propTypes = { |
||||
error: PropTypes.string, |
||||
displayWarning: PropTypes.func, |
||||
importNewAccount: PropTypes.func, |
||||
} |
||||
|
||||
if (!password) { |
||||
const message = 'You must enter a password for the selected file.' |
||||
return this.props.dispatch(actions.displayWarning(message)) |
||||
const mapStateToProps = state => { |
||||
return { |
||||
error: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) |
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
goHome: () => dispatch(actions.goHome()), |
||||
displayWarning: warning => dispatch(actions.displayWarning(warning)), |
||||
importNewAccount: options => dispatch(actions.importNewAccount('JSON File', options)), |
||||
} |
||||
} |
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(JsonImportSubview) |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,53 @@ |
||||
module.exports = { |
||||
timeout, |
||||
queryAsync, |
||||
findAsync, |
||||
pollUntilTruthy, |
||||
} |
||||
|
||||
function timeout (time) { |
||||
return new Promise((resolve, reject) => { |
||||
setTimeout(resolve, time || 1500) |
||||
}) |
||||
} |
||||
|
||||
async function findAsync(container, selector, opts) { |
||||
try { |
||||
return await pollUntilTruthy(() => { |
||||
const result = container.find(selector) |
||||
if (result.length > 0) return result |
||||
}, opts) |
||||
} catch (err) { |
||||
throw new Error(`Failed to find element within interval: "${selector}"`) |
||||
} |
||||
} |
||||
|
||||
async function queryAsync(jQuery, selector, opts) { |
||||
try { |
||||
return await pollUntilTruthy(() => { |
||||
const result = jQuery(selector) |
||||
if (result.length > 0) return result |
||||
}, opts) |
||||
} catch (err) { |
||||
throw new Error(`Failed to find element within interval: "${selector}"`) |
||||
} |
||||
} |
||||
|
||||
async function pollUntilTruthy(fn, opts = {}){ |
||||
const pollingInterval = opts.pollingInterval || 100 |
||||
const timeoutInterval = opts.timeoutInterval || 5000 |
||||
const start = Date.now() |
||||
let result |
||||
while (!result) { |
||||
// check if timedout
|
||||
const now = Date.now() |
||||
if ((now - start) > timeoutInterval) { |
||||
throw new Error(`pollUntilTruthy - failed to return truthy within interval`) |
||||
} |
||||
// check for result
|
||||
result = fn() |
||||
// run again after timeout
|
||||
await timeout(pollingInterval, timeoutInterval) |
||||
} |
||||
return result |
||||
} |
@ -1,106 +1,131 @@ |
||||
const inherits = require('util').inherits |
||||
const Component = require('react').Component |
||||
const PropTypes = require('prop-types') |
||||
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' |
||||
|
||||
module.exports = connect(mapStateToProps)(JsonImportSubview) |
||||
class JsonImportSubview extends Component { |
||||
constructor (props) { |
||||
super(props) |
||||
|
||||
function mapStateToProps (state) { |
||||
return { |
||||
error: state.appState.warning, |
||||
this.state = { |
||||
file: null, |
||||
fileContents: '', |
||||
} |
||||
} |
||||
} |
||||
|
||||
inherits(JsonImportSubview, Component) |
||||
function JsonImportSubview () { |
||||
Component.call(this) |
||||
} |
||||
render () { |
||||
const { error } = this.props |
||||
|
||||
return ( |
||||
h('div.new-account-import-form__json', [ |
||||
|
||||
h('p', t('usedByClients')), |
||||
h('a.warning', { |
||||
href: HELP_LINK, |
||||
target: '_blank', |
||||
}, t('fileImportFail')), |
||||
|
||||
h(FileInput, { |
||||
readAs: 'text', |
||||
onLoad: this.onLoad.bind(this), |
||||
style: { |
||||
margin: '20px 0px 12px 34%', |
||||
fontSize: '15px', |
||||
display: 'flex', |
||||
justifyContent: 'center', |
||||
}, |
||||
}), |
||||
|
||||
h('input.new-account-import-form__input-password', { |
||||
type: 'password', |
||||
placeholder: t('enterPassword'), |
||||
id: 'json-password-box', |
||||
onKeyPress: this.createKeyringOnEnter.bind(this), |
||||
}), |
||||
|
||||
h('div.new-account-create-form__buttons', {}, [ |
||||
|
||||
h('button.new-account-create-form__button-cancel', { |
||||
onClick: () => this.props.goHome(), |
||||
}, [ |
||||
t('cancel'), |
||||
]), |
||||
|
||||
h('button.new-account-create-form__button-create', { |
||||
onClick: () => this.createNewKeychain(), |
||||
}, [ |
||||
t('import'), |
||||
]), |
||||
|
||||
JsonImportSubview.prototype.render = function () { |
||||
const { error } = this.props |
||||
|
||||
return ( |
||||
h('div.new-account-import-form__json', [ |
||||
|
||||
h('p', 'Used by a variety of different clients'), |
||||
h('a.warning', { href: HELP_LINK, target: '_blank' }, 'File import not working? Click here!'), |
||||
|
||||
h(FileInput, { |
||||
readAs: 'text', |
||||
onLoad: this.onLoad.bind(this), |
||||
style: { |
||||
margin: '20px 0px 12px 34%', |
||||
fontSize: '15px', |
||||
display: 'flex', |
||||
justifyContent: 'center', |
||||
}, |
||||
}), |
||||
|
||||
h('input.new-account-import-form__input-password', { |
||||
type: 'password', |
||||
placeholder: 'Enter password', |
||||
id: 'json-password-box', |
||||
onKeyPress: this.createKeyringOnEnter.bind(this), |
||||
}), |
||||
|
||||
h('div.new-account-create-form__buttons', {}, [ |
||||
|
||||
h('button.new-account-create-form__button-cancel', { |
||||
onClick: () => this.props.goHome(), |
||||
}, [ |
||||
'CANCEL', |
||||
]), |
||||
|
||||
h('button.new-account-create-form__button-create', { |
||||
onClick: () => this.createNewKeychain.bind(this), |
||||
}, [ |
||||
'IMPORT', |
||||
]), |
||||
error ? h('span.error', error) : null, |
||||
]) |
||||
) |
||||
} |
||||
|
||||
]), |
||||
onLoad (event, file) { |
||||
this.setState({file: file, fileContents: event.target.result}) |
||||
} |
||||
|
||||
error ? h('span.error', error) : null, |
||||
]) |
||||
) |
||||
} |
||||
createKeyringOnEnter (event) { |
||||
if (event.key === 'Enter') { |
||||
event.preventDefault() |
||||
this.createNewKeychain() |
||||
} |
||||
} |
||||
|
||||
JsonImportSubview.prototype.onLoad = function (event, file) { |
||||
this.setState({file: file, fileContents: event.target.result}) |
||||
} |
||||
createNewKeychain () { |
||||
const state = this.state |
||||
|
||||
JsonImportSubview.prototype.createKeyringOnEnter = function (event) { |
||||
if (event.key === 'Enter') { |
||||
event.preventDefault() |
||||
this.createNewKeychain() |
||||
} |
||||
} |
||||
if (!state) { |
||||
const message = 'You must select a valid file to import.' |
||||
return this.props.displayWarning(message) |
||||
} |
||||
|
||||
JsonImportSubview.prototype.createNewKeychain = function () { |
||||
const state = this.state |
||||
const { fileContents } = state |
||||
|
||||
if (!state) { |
||||
const message = 'You must select a valid file to import.' |
||||
return this.props.dispatch(actions.displayWarning(message)) |
||||
} |
||||
if (!fileContents) { |
||||
const message = t('needImportFile') |
||||
return this.props.displayWarning(message) |
||||
} |
||||
|
||||
const { fileContents } = state |
||||
const passwordInput = document.getElementById('json-password-box') |
||||
const password = passwordInput.value |
||||
|
||||
if (!fileContents) { |
||||
const message = 'You must select a file to import.' |
||||
return this.props.dispatch(actions.displayWarning(message)) |
||||
if (!password) { |
||||
const message = t('needImportPassword') |
||||
return this.props.displayWarning(message) |
||||
} |
||||
|
||||
this.props.importNewJsonAccount([ fileContents, password ]) |
||||
} |
||||
} |
||||
|
||||
const passwordInput = document.getElementById('json-password-box') |
||||
const password = passwordInput.value |
||||
JsonImportSubview.propTypes = { |
||||
error: PropTypes.string, |
||||
goHome: PropTypes.func, |
||||
displayWarning: PropTypes.func, |
||||
importNewJsonAccount: PropTypes.func, |
||||
} |
||||
|
||||
if (!password) { |
||||
const message = 'You must enter a password for the selected file.' |
||||
return this.props.dispatch(actions.displayWarning(message)) |
||||
const mapStateToProps = state => { |
||||
return { |
||||
error: state.appState.warning, |
||||
} |
||||
} |
||||
|
||||
this.props.dispatch(actions.importNewAccount('JSON File', [ fileContents, password ])) |
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
goHome: () => dispatch(actions.goHome()), |
||||
displayWarning: warning => dispatch(actions.displayWarning(warning)), |
||||
importNewJsonAccount: options => dispatch(actions.importNewAccount('JSON File', options)), |
||||
} |
||||
} |
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(JsonImportSubview) |
||||
|
@ -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); |
||||
} |
||||
} |
@ -0,0 +1,56 @@ |
||||
import EventEmitter from 'events' |
||||
import h from 'react-hyperscript' |
||||
import { Component } from 'react' |
||||
import PropTypes from 'prop-types' |
||||
import {connect} from 'react-redux' |
||||
import {closeWelcomeScreen} from './actions' |
||||
import Mascot from './components/mascot' |
||||
|
||||
class WelcomeScreen extends Component { |
||||
static propTypes = { |
||||
closeWelcomeScreen: PropTypes.func.isRequired, |
||||
} |
||||
|
||||
constructor(props) { |
||||
super(props) |
||||
this.animationEventEmitter = new EventEmitter() |
||||
} |
||||
|
||||
initiateAccountCreation = () => { |
||||
this.props.closeWelcomeScreen() |
||||
} |
||||
|
||||
render () { |
||||
return h('div.welcome-screen', [ |
||||
|
||||
h('div.welcome-screen__info', [ |
||||
|
||||
h(Mascot, { |
||||
animationEventEmitter: this.animationEventEmitter, |
||||
width: '225', |
||||
height: '225', |
||||
}), |
||||
|
||||
h('div.welcome-screen__info__header', 'Welcome to MetaMask Beta'), |
||||
|
||||
h('div.welcome-screen__info__copy', 'MetaMask is a secure identity vault for Ethereum.'), |
||||
|
||||
h('div.welcome-screen__info__copy', `It allows you to hold ether & tokens,
|
||||
and serves as your bridge to decentralized applications.`),
|
||||
|
||||
h('button.welcome-screen__button', { |
||||
onClick: this.initiateAccountCreation, |
||||
}, 'Continue'), |
||||
|
||||
]), |
||||
|
||||
]) |
||||
} |
||||
} |
||||
|
||||
export default connect( |
||||
null, |
||||
dispatch => ({ |
||||
closeWelcomeScreen: () => dispatch(closeWelcomeScreen()), |
||||
}) |
||||
)(WelcomeScreen) |
@ -0,0 +1,33 @@ |
||||
|
||||
// cross-browser connection to extension i18n API
|
||||
|
||||
const chrome = chrome || null |
||||
const browser = browser || null |
||||
const extension = require('extensionizer') |
||||
var log = require('loglevel') |
||||
window.log = log |
||||
let getMessage |
||||
|
||||
if (extension.i18n && extension.i18n.getMessage) { |
||||
getMessage = extension.i18n.getMessage |
||||
} else { |
||||
// fallback function
|
||||
log.warn('browser.i18n API not available, calling back to english.') |
||||
const msg = require('../app/_locales/en/messages.json') |
||||
getMessage = function (key, substitutions) { |
||||
if (!msg[key]) { |
||||
log.error(key) |
||||
throw new Error(key) |
||||
} |
||||
let phrase = msg[key].message |
||||
if (substitutions && substitutions.length) { |
||||
phrase = phrase.replace(/\$1/g, substitutions[0]) |
||||
if (substitutions.length > 1) { |
||||
phrase = phrase.replace(/\$2/g, substitutions[1]) |
||||
} |
||||
} |
||||
return phrase |
||||
} |
||||
} |
||||
|
||||
module.exports = getMessage |
Loading…
Reference in new issue