diff options
49 files changed, 3983 insertions, 217 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e7d4a09fe..75ba7670f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Current Master +- MetaMask will no longer allow nonces to be specified by the dapp - 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. diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 35a360c84..b309b119d 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -37,6 +37,9 @@ "message": "MetaMask", "description": "The name of the application" }, + "approved": { + "message": "Approved" + }, "attemptingConnect": { "message": "Attempting to connect to blockchain." }, @@ -83,6 +86,9 @@ "buyCoinbaseExplainer": { "message": "Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin." }, + "ok": { + "message": "Ok" + }, "cancel": { "message": "Cancel" }, @@ -95,6 +101,9 @@ "confirm": { "message": "Confirm" }, + "confirmed": { + "message": "Confirmed" + }, "confirmContract": { "message": "Confirm Contract" }, @@ -173,7 +182,7 @@ }, "decimal": { "message": "Decimals of Precision" - }, + }, "defaultNetwork": { "message": "The default network for Ether transactions is Main Net." }, @@ -226,6 +235,9 @@ "downloadStatelogs": { "message": "Download State Logs" }, + "dropped": { + "message": "Dropped" + }, "edit": { "message": "Edit" }, @@ -244,6 +256,12 @@ "enterPasswordConfirm": { "message": "Enter your password to confirm" }, + "passwordNotLongEnough": { + "message": "Password not long enough" + }, + "passwordsDontMatch": { + "message": "Passwords Don't Match" + }, "etherscanView": { "message": "View account on Etherscan" }, @@ -403,6 +421,9 @@ "knowledgeDataBase": { "message": "Visit our Knowledge Base" }, + "max": { + "message": "Max" + }, "lessThanMax": { "message": "must be less than or equal to $1.", "description": "helper for inputting hex as decimal input" @@ -410,6 +431,9 @@ "likeToAddTokens": { "message": "Would you like to add these tokens?" }, + "links": { + "message": "Links" + }, "limit": { "message": "Limit" }, @@ -583,12 +607,18 @@ "restoreFromSeed": { "message": "Restore from seed phrase" }, + "restoreVault": { + "message": "Restore Vault" + }, "required": { "message": "Required" }, "retryWithMoreGas": { "message": "Retry with a higher gas price here" }, + "walletSeed": { + "message": "Wallet Seed" + }, "revealSeedWords": { "message": "Reveal Seed Words" }, @@ -604,6 +634,24 @@ "ropsten": { "message": "Ropsten Test Network" }, + "currentRpc": { + "message": "Current RPC" + }, + "connectingToMainnet": { + "message": "Connecting to Main Ethereum Network" + }, + "connectingToRopsten": { + "message": "Connecting to Ropsten Test Network" + }, + "connectingToKovan": { + "message": "Connecting to Kovan Test Network" + }, + "connectingToRinkeby": { + "message": "Connecting to Rinkeby Test Network" + }, + "connectingToUnknown": { + "message": "Connecting to Unknown Network" + }, "sampleAccountName": { "message": "E.g. My new account", "description": "Help user understand concept of adding a human-readable name to their account" @@ -611,6 +659,12 @@ "save": { "message": "Save" }, + "reprice:title": { + "message": "Reprice Transaction" + }, + "reprice:subtitle": { + "message": "Increase your gas price to attempt to overwrite and speed up your transaction" + }, "saveAsFile": { "message": "Save as File", "description": "Account export process" @@ -624,6 +678,9 @@ "secretPhrase": { "message": "Enter your secret twelve word phrase here to restore your vault." }, + "newPassword8Chars": { + "message": "New Password (min 8 chars)" + }, "seedPhraseReq": { "message": "seed phrases are 12 words long" }, @@ -648,12 +705,18 @@ "sendTokens": { "message": "Send Tokens" }, + "onlySendToEtherAddress": { + "message": "Only send ETH to an Ethereum address." + }, "sendTokensAnywhere": { "message": "Send Tokens to anyone with an Ethereum account" }, "settings": { "message": "Settings" }, + "info": { + "message": "Info" + }, "shapeshiftBuy": { "message": "Buy with Shapeshift" }, @@ -666,6 +729,9 @@ "sign": { "message": "Sign" }, + "signed": { + "message": "Signed" + }, "signMessage": { "message": "Sign Message" }, @@ -690,9 +756,15 @@ "stateLogsDescription": { "message": "State logs contain your public account addresses and sent transactions." }, + "stateLogError": { + "message": "Error in retrieving state logs." + }, "submit": { "message": "Submit" }, + "submitted": { + "message": "Submitted" + }, "supportCenter": { "message": "Visit our Support Center" }, @@ -709,7 +781,7 @@ "message": "Test Faucet" }, "to": { - "message": "To" + "message": "To: " }, "toETHviaShapeShift": { "message": "$1 to ETH via ShapeShift", @@ -764,6 +836,9 @@ "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." }, + "unapproved": { + "message": "Unapproved" + }, "unavailable": { "message": "Unavailable" }, @@ -800,7 +875,7 @@ }, "visitWebSite": { "message": "Visit our web site" - }, + }, "warning": { "message": "Warning" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index 78fc64dbf..f519c194c 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -1,10 +1,840 @@ { + "accept": { + "message": "Aceptar" + }, + "account": { + "message": "Cuenta" + }, + "accountDetails": { + "message": "Detalles de la cuenta" + }, + "accountName": { + "message": "Nombre de la cuenta" + }, + "address": { + "message": "Dirección" + }, + "addToken": { + "message": "Agregar Token" + }, + "amount": { + "message": "Cantidad" + }, + "amountPlusGas": { + "message": "Cantidad + Gas" + }, + "appDescription": { + "message": "Extensión del explorador usar Ethereum", + "description": "La descripción de la aplicación" + }, "appName": { "message": "MetaMask", - "description": "The name of the application" + "description": "El nombre de la aplicación" }, - "appDescription": { - "message": "Administración de identidad en Ethereum", - "description": "The description of the application" + "attemptingConnect": { + "message": "Intentando conectar a la Blockchain" + }, + "available": { + "message": "Disponible" + }, + "back": { + "message": "Atrás" + }, + "balance": { + "message": "Saldo" + }, + "balances": { + "message": "Tus saldos" + }, + "balanceIsInsufficientGas": { + "message": "Saldo de gas insuficiente" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "Debe ser mayor o igual a $1 y menor o igual a $2", + "description": "helper para ingresar hex como un ingreso decimal" + }, + "borrowDharma": { + "message": "Pedir prestado con Dharma (Beta)" + }, + "buy": { + "message": "Comprar" + }, + "buyCoinbase": { + "message": "Comprar en Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase es la manera más popular en el mundo para comprar y vender bitcoin, ethereum y litecoin" + }, + "cancel": { + "message": "Cancelar" + }, + "clickCopy": { + "message": "Click para copiar" + }, + "confirm": { + "message": "Confirmar" + }, + "continue": { + "message": "Continuar" + }, + "confirmContract": { + "message": "Confirmar contrato" + }, + "confirmPassword": { + "message": "Confirmar contraseña" + }, + "enterPasswordConfirm": { + "message": "Ingresa tu contraseña para confirmar" + }, + "confirmTransaction": { + "message": "Confirmar transacción " + }, + "continueToCoinbase": { + "message": "Continuar a Coinbase" + }, + "contractDeployment": { + "message": "Deployar contrato" + }, + "currentConversion": { + "message": "Conversión Actual" + }, + "conversionProgress": { + "message": "Conversión en progreso" + }, + "copiedButton": { + "message": "Copiado" + }, + "copiedClipboard": { + "message": "Copiado al portapapeles" + }, + "copiedExclamation": { + "message": "Copiado!" + }, + "copy": { + "message": "Copiar" + }, + "copyToClipboard": { + "message": "Copiar al portapapeles" + }, + "copyButton": { + "message": " Copiar " + }, + "copyPrivateKey": { + "message": "Esta es tu llave privada (Click para copiar)" + }, + "create": { + "message": "Crear" + }, + "createAccount": { + "message": "Crear Cuenta" + }, + "createDen": { + "message": "Crear" + }, + "crypto": { + "message": "Crypto", + "description": "Tipo de Cambio (criptomonedas)" + }, + "customGas": { + "message": "Personalizar Gas" + }, + "customize": { + "message": "Personalizar" + }, + "currentRPC": { + "message": "RPC actual" + }, + "customRPC": { + "message": "RPC Personalizado" + }, + "newRPC": { + "message": "Nueva URL del RPC" + }, + "defaultNetwork": { + "message": "La red por defecto para las transacciones de Ether es MainNet (red principal)" + }, + "denExplainer": { + "message": "Tu DEN es tu contraseña encriptada guardada dentro de MetaMask" + }, + "deposit": { + "message": "Depositar" + }, + "depositBTC": { + "message": "Deposita tus BTC a la dirección de abajo:" + }, + "depositCoin": { + "message": "Deposita tu $1 a la dirección de abajo", + "description": "Informa al usuario que moneda ha elegido para depositar en shapeshift" + }, + "depositEth": { + "message": "Depositar Eth" + }, + "depositEther": { + "message": "Depositar Ether" + }, + "depositFiat": { + "message": "Depositar con Fiat (divisa nacional)" + }, + "depositFromAccount": { + "message": "Depositar con otra cuenta" + }, + "depositShapeShift": { + "message": "Depositar con ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Si tu tienes otras criptomonedas, puedes intercambiar y depositar Ether directamente en tu billetera de MetaMask. No necesitas tener una cuenta." + }, + "details": { + "message": "Detalles" + }, + "directDeposit": { + "message": "Deposito directo" + }, + "directDepositEther": { + "message": "Depositar Ether directamente" + }, + "directDepositEtherExplainer": { + "message": "Si tu tienes algo de Ether, la forma rapida para tener Ether en tu nueva billetera es depositando directamente" + }, + "done": { + "message": "Completo" + }, + "edit": { + "message": "Editar" + }, + "editAccountName": { + "message": "Editar el nombre de la cuenta" + }, + "encryptNewDen": { + "message": "Encriptar tu nuevo DEN" + }, + "enterPassword": { + "message": "Ingresa contraseña" + }, + "passwordCorrect": { + "message": "Asegurate que tu contraseña es correcta" + }, + "etherscanView": { + "message": "Ver la cuenta en Etherscan" + }, + "exchangeRate": { + "message": "Tipo de cambio" + }, + "exportPrivateKey": { + "message": "Exportar llave privada" + }, + "exportPrivateKeyWarning": { + "message": "Exportar llaves privadas bajo TU PROPIO riesgo" + }, + "failed": { + "message": "Fallo" + }, + "fiat": { + "message": "FIAT", + "description": "Exchange type" + }, + "fileImportFail": { + "message": "No funciona importar el archivo? Haz Click Aquí!", + "description": "Ayuda al usuario a importar su cuenta desde un archivo JSON" + }, + "from": { + "message": "De:" + }, + "fromShapeShift": { + "message": "De ShapeShift" + }, + "gas": { + "message": "Gas", + "description": "Indicación pequeña del costo de gas" + }, + "gasFee": { + "message": "Comisión de gas" + }, + "gasLimit": { + "message": "Límite de gas" + }, + "gasLimitCalculation": { + "message": "Calculamos el límite de gas sugerido en función de las tasas de éxito de la red" + }, + "gasLimitRequired": { + "message": "Límite de Gas requerido" + }, + "gasLimitTooLow": { + "message": "El límite de gas debe ser de al menos 21000" + }, + "gasPrice": { + "message": "Precio del Gas (GWEI)" + }, + "gasPriceCalculation": { + "message": "Calculamos los precios sugeridos del gas en función de las tasas de éxito de la red" + }, + "gasPriceRequired": { + "message": "Precio del gas requerido" + }, + "getEther": { + "message": "Conseguir Ether" + }, + "getEtherFromFaucet": { + "message": "Obtenga Ether de un faucet (grifo) por $1", + "description": "Muestra el nombre de la red para el faucet (grifo) de Ether" + }, + "greaterThanMin": { + "message": "Debe ser mayor o igual a $1", + "description": "helper para ingresar hex como entrada decimal" + }, + "here": { + "message": "Aqui", + "description": "como en -haz click aquí- para más información" + }, + "hide": { + "message": "Ocultar" + }, + "hideToken": { + "message": "Ocultar Token" + }, + "hideTokenPrompt": { + "message": "Ocultar Token?" + }, + "howToDeposit": { + "message": "Cómo te gustaria depositar Ether?" + }, + "import": { + "message": "Importar", + "description": "Botón para importar una cuenta desde un archivo seleccionado" + }, + "importAccount": { + "message": "Importar Cuenta" + }, + "importAnAccount": { + "message": "Importar una cuenta" + }, + "importDen": { + "message": "Importar DEN existente" + }, + "imported": { + "message": "Importado", + "description": "estado que muestra que una cuenta ha sido completamente cargada en el llavero" + }, + "infoHelp": { + "message": "Informacion y Ayuda" + }, + "invalidAddress": { + "message": "Dirección Inválida" + }, + "invalidGasParams": { + "message": "Parametros de Gas Inválidos" + }, + "invalidInput": { + "message": "Entrada inválida" + }, + "invalidRequest": { + "message": "Peticion inválida" + }, + "invalidAddressRecipient": { + "message": "Dirección del recipiente invalida" + }, + "fromToSame": { + "message": "La dirección de origen y destino no pueden ser la misma" + }, + "jsonFile": { + "message": "Archivo JSON", + "description": "formato para importar una cuenta" + }, + "jsonFail": { + "message": "Algo malo pasó. Asegurate que tu JSON tiene el formato correcto" + }, + "kovan": { + "message": "Red de pruebas Kovan" + }, + "lessThanMax": { + "message": "Debe ser menor o igual a $1", + "description": "helper para ingresar hex como decimal" + }, + "limit": { + "message": "Límite" + }, + "loading": { + "message": "Cargando..." + }, + "loadingTokens": { + "message": "Cargando Tokens..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Ingresar" + }, + "logout": { + "message": "Cerrar sesion" + }, + "loose": { + "message": "Loose" + }, + "mainnet": { + "message": "Red principal de Ethereum (MainNet)" + }, + "message": { + "message": "Mensaje" + }, + "min": { + "message": "Minimo" + }, + "myAccounts": { + "message": "Mis cuentas" + }, + "needEtherInWallet": { + "message": "Para interactuar con una aplicación descentralizada usando MetaMask, vas a necesitar tener Ether en tu billetera" + }, + "needImportFile": { + "message": "Debes seleccionar un archivo para importar", + "description": "El usuario está importando una cuenta y necesita agregar un archivo para continuar" + }, + "needImportPassword": { + "message": "Debes ingresar una contraseña para el archivo seleccionado", + "description": "Contraseña y archivo necesarios para importar una cuenta" + }, + "validFileImport": { + "message": "Debes selecionar un archivo valido para importar" + }, + "networks": { + "message": "Redes" + }, + "newAccount": { + "message": "Nueva cuenta" + }, + "newAccountNumberName": { + "message": "Cuenta $1", + "description": "Nombre por defecto de la próxima cuenta a ser creada o pantalla de creación de cuenta" + }, + "newContract": { + "message": "Nuevo contrato" + }, + "newPassword": { + "message": "Nueva contraseña (mínimo [8] caracteres)" + }, + "newRecipient": { + "message": "Nuevo destinatario" + }, + "next": { + "message": "Siguiente" + }, + "noAddressForName": { + "message": "No se ha establecido ninguna dirección para este nombre" + }, + "noDeposits": { + "message": "No hay depósitos recibidos" + }, + "noTransactionHistory": { + "message": "Sin historial de transacciones" + }, + "transactions": { + "message": "Transacciones" + }, + "noTransactions": { + "message": "Sin transacciones" + }, + "notStarted": { + "message": "Sin iniciar" + }, + "oldUI": { + "message": "Antigua UI" + }, + "useOldUI": { + "message": "Usar UI antigua" + }, + "oldUIMessage": { + "message": "Regresaste a la antigua UI. Puedes regresar a la nueva UI mediante la opcion en la barra desplegable del menu de arriba a la derecha." + }, + "or": { + "message": "o", + "description": "opción entre crear o importar una cuenta" + }, + "passwordMismatch": { + "message": "Contraseña no concide", + "description": "en el proceso de creación de contraseña, los dos campos de contraseña no coincidieron" + }, + "passwordShort": { + "message": "Contraseña no es lo suficientemente larga", + "description": "in password creation process, the password is not long enough to be secure" + }, + "pastePrivateKey": { + "message": "Pega tu llave privada aqui", + "description": "Para importar una cuenta desde una llave privada" + }, + "pasteSeed": { + "message": "Pegue su frase semilla aquí!" + }, + "generatingSeed": { + "message": "Generando semilla..." + }, + "pleaseReviewTransaction": { + "message": "Por favor revisa tu transaccion" + }, + "privateKey": { + "message": "Llave privada", + "description": "select this type of file to use to import an account" + }, + "privateKeyWarning": { + "message": "Advertencia: Nunca revele esta clave. Cualquier persona con sus claves privadas puede robar cualquier activo retenido en su cuenta." + }, + "privateNetwork": { + "message": "Red Privada" + }, + "qrCode": { + "message": "Mostrar codigo QR" + }, + "readdToken": { + "message": "Puede volver a agregar este token en el futuro yendo a 'Agregar token' en el menú de opciones de su cuenta.." + }, + "readMore": { + "message": "Leer más aquí" + }, + "receive": { + "message": "Recibir" + }, + "recipientAddress": { + "message": "Dirección del receptor" + }, + "refundAddress": { + "message": "Su dirección de reembolso" + }, + "rejected": { + "message": "Rechazado" + }, + "required": { + "message": "Requerido" + }, + "retryWithMoreGas": { + "message": "Vuelva a intentar con un precio de Gas más alto aquí" + }, + "revert": { + "message": "Revertir" + }, + "rinkeby": { + "message": "Red privada Rinkeby" + }, + "ropsten": { + "message": "Red privada Ropsten" + }, + "sampleAccountName": { + "message": "Ej. Mi nueva cuenta", + "description": "Ayuda al usuario a entender el concepto de agregar un nombre, leíble por humanos, a su cuenta" + }, + "save": { + "message": "Guardar" + }, + "saveAsFile": { + "message": "Guardar como archivo", + "description": "Proceso de exportación de cuenta" + }, + "selectService": { + "message": "Seleccionar servicio" + }, + "send": { + "message": "Enviar" + }, + "sendTokens": { + "message": "Enviar Tokens" + }, + "sendETH": { + "message": "Enviar ETH" + }, + "sendTokensAnywhere": { + "message": "Enviar Tokens a cualquiera con una cuenta de Ethereum" + }, + "settings": { + "message": "Configuración" + }, + "info": { + "message": "Información" + }, + "shapeshiftBuy": { + "message": "Comprar con ShapeShift" + }, + "showPrivateKeys": { + "message": "Mostrar llaves privadas" + }, + "showQRCode": { + "message": "Mostrar codigo QR" + }, + "sign": { + "message": "Firmar" + }, + "signMessage": { + "message": "Firmar Mensaje" + }, + "signNotice": { + "message": "Firmar este mensaje puede tener\n efectos secundarios peligrosos. Firma sólo\nmensajes desde sitios a los cuales tú estés dispuesto a confiar completamente tu cuenta.\nEste método peligroso va a ser \nremovido en una version futura." + }, + "sigRequest": { + "message": "Solicitud de firma" + }, + "sigRequested": { + "message": "Firma solicitada" + }, + "status": { + "message": "Estado" + }, + "submit": { + "message": "Enviar" + }, + "takesTooLong": { + "message": "¿Está tomando demasiado?" + }, + "testFaucet": { + "message": "Testear Faucet" + }, + "to": { + "message": "Para:" + }, + "toETHviaShapeShift": { + "message": "$1 a ETH via ShapeShift", + "description": "el sistema llenará el tipo de depósito al principio del mensaje" + }, + "tokenBalance": { + "message": "Tu balance de Tokens es:" + }, + "total": { + "message": "Total" + }, + "transactionMemo": { + "message": "Memo de transaccion (opcional)" + }, + "transactionNumber": { + "message": "Número de transacción" + }, + "transfers": { + "message": "Transferencias" + }, + "troubleTokenBalances": { + "message": "Tuvimos problemas para cargar sus balances de tokens. Puedes verlos ", + "description": "Followed by a link (here) to view token balances" + }, + "typePassword": { + "message": "Escribe tu contraseña" + }, + "uiWelcome": { + "message": "Bienvenido a la nueva UI (Beta)" + }, + "uiWelcomeMessage": { + "message": "Estás usando la nueva UI de MetaMask. Echa un vistazo alrededor, prueba las nuevas características, tales como mandar tokens, y déjanos saber si tienes algún problema" + }, + "unavailable": { + "message": "No disponible" + }, + "unknown": { + "message": "Desconocido (a)" + }, + "unknownNetwork": { + "message": "Red privada desconocida" + }, + "unknownNetworkId": { + "message": "ID (identidad) de Red desconocida" + }, + "currentNetwork": { + "message": "Red actual" + }, + "usaOnly": { + "message": "Sólo USA (Estados Unidos)", + "description": "El uso de este exchange (casa de cambio) estaá limitado a las personas dentro de los Estados Unidos de America" + }, + "usedByClients": { + "message": "Utilizado por una variedad de clientes diferentes" + }, + "viewAccount": { + "message": "Mirar cuenta" + }, + "warning": { + "message": "Advertencia" + }, + "whatsThis": { + "message": "Qué es esto?" + }, + "yourSigRequested": { + "message": "Tu firma ya fue solicidada" + }, + "youSign": { + "message": "Usted está firmando" + }, + "importedAccountMsg": { + "message": "Cuentas importadas no serán asociadas con tu cuenta original creada con tu MetaMask. Aprende más acerca de importar cuentas. " + }, + "selectType": { + "message": "Seleccionar tipo" + }, + "selectCurrency": { + "message": "Seleccionar moneda" + }, + "password": { + "message": "Contraseña" + }, + "select": { + "message": "Seleccionar" + }, + "readMore2": { + "message": "Leer más." + }, + "secretPhrase": { + "message": "Ingresa tu frase de 12 palabras para restaurar tu bóveda" + }, + "spaceBetween": { + "message": "Sólo puede haber un espacio entre las palabras" + }, + "loweCaseWords": { + "message": "las frases semilla sólo pueden tener minúsculas" + }, + "seedPhraseReq": { + "message": "las frases semilla tienen doce (12) palabras de largo" + }, + "addTokens": { + "message": "Agregar tokens" + }, + "addCustomTokens": { + "message": "Agregar token personalizados" + }, + "up": { + "message": "arriba" + }, + "down": { + "message": "abajo" + }, + "tokenWarning1": { + "message": "Mantenga un registro de los tokens que ha comprado con su cuenta de MetaMask. Si compraste tokens usando una cuenta diferente, esos tokens no aparecerán aquí." + }, + "tokenSelection": { + "message": "Busca tokens o selecciónalo de nuestra lista de tokens populares" + }, + "search": { + "message": "Buscar" + }, + "privacyMsg": { + "message": "Política de privacidad" + }, + "terms": { + "message": "Terminos de Uso" + }, + "attributions": { + "message": "Atribuciones" + }, + "supportCenter": { + "message": "Visita nuestro centro de atención" + }, + "knowledgeDataBase": { + "message": "Visita nuestra base de conocimiento" + }, + "visitWebSite": { + "message": "Visita nuestro sitio web" + }, + "followTwitter": { + "message": "Síguenos en Twitter" + }, + "emailUs": { + "message": "Envíanos un correo!" + }, + "hereList": { + "message": "Aquí está una lista!!!" + }, + "insufficientFounds": { + "message": "Fondos insuficientes" + }, + "insufficientTokens": { + "message": "Tokens insuficientes" + }, + "negativeETH": { + "message": "No se pueden mandar cantidades negativas de ETH" + }, + "urlErrorMsg": { + "message": "URI necesita el prefijo HTTP/HTTPS apropiado" + }, + "invalidRPC": { + "message": "Invalida URL del RPC" + }, + "saveLogs": { + "message": "Logs de estado contienen tus direcciones publicas y transacciones enviadas" + }, + "stateLogs": { + "message": "Logs de estado" + }, + "downloadStatelogs": { + "message": "Descargar logs de estados" + }, + "revealSeedWords": { + "message": "Revelar palabras de semilla" + }, + "revealSeedWordsWarning": { + "message": "No recuperes tu semilla en un lugar publico! Esas palabras pueden ser usadas para robarte todas tus cuentas" + }, + "resetAccount": { + "message": "Reiniciar cuenta" + }, + "builtInCalifornia": { + "message": "Metamask fue diseñado y construido en California " + }, + "classicInterface": { + "message": "Usar interfaz clasica " + }, + "welcomeBeta": { + "message": "Bienvenido a Metamask Beta" + }, + "metamaskDescription": { + "message": "Metamask es una identidad segura en Ethereum" + }, + "holdEther": { + "message": "Te permite mantener tus ether y tokens, así como puente para aplicaciones descentralizadas" + }, + "decimalsMustZerotoTen": { + "message": "Los decimales deben ser al menos 0 y no más de 36" + }, + "symbolBetweenZeroTen": { + "message": "Símbolo debe ser entre 0 y 10 caracteres" + }, + "personalAddressDetected": { + "message": "Dirección personal detectada. Ingresa la dirección del contrato del token" + }, + "tokenAlreadyAdded": { + "message": "El token esta actualmente agregado" + }, + "mustSelectOne": { + "message": "Debe seleccionar al menos un (1) token" + }, + "tokenAddress": { + "message": "Dirección del token" + }, + "tokenSymbol": { + "message": "Símbolo del token" + }, + "decimalsPrecision": { + "message": "Decimales de precisión" + }, + "likeToAddTokens": { + "message": "¿Te gustaría agregar estos tokens?" + }, + "msgCompose1": { + "message": "Solo manda " + }, + "msgCompose2": { + "message": " a una dirección de Ethereum" + }, + "blockiesIdenticon": { + "message": "Usar Blockies Identicon (Iconos)" + }, + "vaultCreated": { + "message": "Bóveda creada" + }, + "twelveWords": { + "message": "Estas 12 palabras son la única forma de restablecer sus cuentas de MetaMask. \nGuardalas en un lugar seguro y secreto." + }, + "copiedSafe": { + "message": "Ya lo guardé en un lugar seguro" + }, + "saveSeedAsFile": { + "message": "Guardar la semilla como archivo" + }, + "restoreFromSeed": { + "message": "Restaurar desde semilla" } -} +}
\ No newline at end of file diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json new file mode 100644 index 000000000..f54ef98ca --- /dev/null +++ b/app/_locales/it/messages.json @@ -0,0 +1,819 @@ +{ + "accept": { + "message": "Accetta" + }, + "account": { + "message": "Account" + }, + "accountDetails": { + "message": "Dettagli Account" + }, + "accountName": { + "message": "Nome Account" + }, + "address": { + "message": "Indirizzo" + }, + "addCustomToken": { + "message": "Aggiungi un token personalizzato" + }, + "addToken": { + "message": "Aggiungi Token" + }, + "addTokens": { + "message": "Aggiungi più token" + }, + "amount": { + "message": "Importo" + }, + "amountPlusGas": { + "message": "Importo + Gas" + }, + "appDescription": { + "message": "Ethereum Browser Extension", + "description": "La descrizione dell'applicazione" + }, + "appName": { + "message": "MetaMask", + "description": "Il nome dell'applicazione" + }, + "attemptingConnect": { + "message": "Tentativo di connessione alla blockchain." + }, + "attributions": { + "message": "Attribuzioni" + }, + "available": { + "message": "Disponibile" + }, + "back": { + "message": "Indietro" + }, + "balance": { + "message": "Bilancio:" + }, + "balances": { + "message": "I tuoi bilanci" + }, + "balanceIsInsufficientGas": { + "message": "Bilancio insufficiente per il gas totale corrente" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "deve essere maggiore o uguale a $1 e minore o uguale a $2.", + "description": "aiuto per inserire un input esadecimale come decimale" + }, + "blockiesIdenticon": { + "message": "Usa le icone Blockie" + }, + "borrowDharma": { + "message": "Prendi in presisito con Dharma (Beta)" + }, + "builtInCalifornia": { + "message": "MetaMask è progettato e costruito in California." + }, + "buy": { + "message": "Compra" + }, + "buyCoinbase": { + "message": "Compra su Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase è il servizio più popolare al mondo per comprare e vendere bitcoin, ethereum e litecoin." + }, + "cancel": { + "message": "Cancella" + }, + "classicInterface": { + "message": "Usa l'interfaccia classica" + }, + "clickCopy": { + "message": "Clicca per Copiare" + }, + "confirm": { + "message": "Conferma" + }, + "confirmContract": { + "message": "Conferma Contratto" + }, + "confirmPassword": { + "message": "Conferma Password" + }, + "confirmTransaction": { + "message": "Conferma Transazione" + }, + "continue": { + "message": "Continua" + }, + "continueToCoinbase": { + "message": "Continua su Coinbase" + }, + "contractDeployment": { + "message": "Distribuzione Contratto" + }, + "conversionProgress": { + "message": "Conversione in corso" + }, + "copiedButton": { + "message": "Copiato" + }, + "copiedClipboard": { + "message": "Copiato negli Appunti" + }, + "copiedExclamation": { + "message": "Copiato!" + }, + "copiedSafe": { + "message": "Le ho copiate in un posto sicuro" + }, + "copy": { + "message": "Copia" + }, + "copyToClipboard": { + "message": "Copia negli appunti" + }, + "copyButton": { + "message": " Copia " + }, + "copyPrivateKey": { + "message": "Questa è la tua chiave privata (clicca per copiare)" + }, + "create": { + "message": "Crea" + }, + "createAccount": { + "message": "Crea Account" + }, + "createDen": { + "message": "Crea" + }, + "crypto": { + "message": "Crypto", + "description": "Tipo di exchange (cryptomonete)" + }, + "currentConversion": { + "message": "Cambio Corrente" + }, + "currentNetwork": { + "message": "Rete Corrente" + }, + "customGas": { + "message": "Personalizza Gas" + }, + "customize": { + "message": "Personalizza" + }, + "customRPC": { + "message": "RPC Personalizzata" + }, + "decimalsMustZerotoTen": { + "message": "Il numero di decimali deve essere almeno 0, e non oltre 36." + }, + "decimal": { + "message": "Precisione Decimali" + }, + "defaultNetwork": { + "message": "La rete predefinita per transazioni in Ether è la Rete Ethereum Principale." + }, + "denExplainer": { + "message": "Il DEN è il tuo archivio crittato con password dentro Metamask." + }, + "deposit": { + "message": "Deposita" + }, + "depositBTC": { + "message": "Deposita i tuoi BTC all'indirizzo sotto:" + }, + "depositCoin": { + "message": "Deposita $1 all'indirizzo sotto", + "description": "Dice all'utente quale moneta ha selezionato per depositare con Shapeshift" + }, + "depositEth": { + "message": "Deposita Eth" + }, + "depositEther": { + "message": "Deposita Ether" + }, + "depositFiat": { + "message": "Deposita con moneta Fiat" + }, + "depositFromAccount": { + "message": "Deposita da un altro account" + }, + "depositShapeShift": { + "message": "Deposita con ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Se possiedi altre criptomonete, puoi scambiare e depositare Ether direttamente nel tuo portafoglio MetaMask. Nessun account richiesto." + }, + "details": { + "message": "Dettagli" + }, + "directDeposit": { + "message": "Deposito Diretto" + }, + "directDepositEther": { + "message": "Deposita Direttamente Ether" + }, + "directDepositEtherExplainer": { + "message": "Se possiedi già degli Ether, questa è la via più veloce per aggiungere Ether al tuo portafoglio con un deposito diretto." + }, + "done": { + "message": "Finito" + }, + "downloadStatelogs": { + "message": "Scarica i log di Stato" + }, + "edit": { + "message": "Modifica" + }, + "editAccountName": { + "message": "Modifica Nome Account" + }, + "emailUs": { + "message": "Mandaci una mail!" + }, + "encryptNewDen": { + "message": "Cripta il tuo nuovo DEN" + }, + "enterPassword": { + "message": "Inserisci password" + }, + "enterPasswordConfirm": { + "message": "Inserisci la tua password per confermare" + }, + "etherscanView": { + "message": "Vedi account su Etherscan" + }, + "exchangeRate": { + "message": "Tasso di cambio" + }, + "exportPrivateKey": { + "message": "Esporta Chiave Privata" + }, + "exportPrivateKeyWarning": { + "message": "Esporta chiave privata a tuo rischio." + }, + "failed": { + "message": "Fallito" + }, + "fiat": { + "message": "FIAT", + "description": "Tipo di scambio" + }, + "fileImportFail": { + "message": "Importazione file non funziona? Clicca qui!", + "description": "Aiuta gli utenti a importare il loro account da un file JSON" + }, + "followTwitter": { + "message": "Seguici su Twitter" + }, + "from": { + "message": "Da" + }, + "fromToSame": { + "message": "Gli indirizzi Da e A non possono essere uguali" + }, + "fromShapeShift": { + "message": "Da ShapeShift" + }, + "gas": { + "message": "Gas", + "description": "Piccola indicazione del costo del gas" + }, + "gasFee": { + "message": "Costo Gas" + }, + "gasLimit": { + "message": "Gas Limite" + }, + "gasLimitCalculation": { + "message": "Calcoliamo il gas limite suggerito in base al successo delle transazioni in rete." + }, + "gasLimitRequired": { + "message": "Gas Limite Richiesto" + }, + "gasLimitTooLow": { + "message": "Il Gas Limite deve essere almeno 21000" + }, + "generatingSeed": { + "message": "Generando la frase seed..." + }, + "gasPrice": { + "message": "Prezzo del Gas (GWEI)" + }, + "gasPriceCalculation": { + "message": "Calcoliamo il gas limite suggerito in base al successo delle transazioni in rete." + }, + "gasPriceRequired": { + "message": "Prezzo Gas Richiesto" + }, + "getEther": { + "message": "Ottieni Ether" + }, + "getEtherFromFaucet": { + "message": "Ottieni Get Ether da un faucet per $1", + "description": "Visualizza il nome della rete per il faucet Ether" + }, + "greaterThanMin": { + "message": "deve essere maggiore o uguale a $1.", + "description": "aiuto per inserire un input esadecimale come decimale" + }, + "here": { + "message": "qui", + "description": "per intendere -clicca qui- per maggiori informazioni (va con troubleTokenBalances)" + }, + "hereList": { + "message": "Questa è una lista!!!!" + }, + "hide": { + "message": "Nascondi" + }, + "hideToken": { + "message": "Nascondi Token" + }, + "hideTokenPrompt": { + "message": "Nascondi Token?" + }, + "howToDeposit": { + "message": "Come vuoi depositare Ether?" + }, + "holdEther": { + "message": "Ti permette di tenere ether & token, e serve da ponte per le applicazioni decentralizzate." + }, + "import": { + "message": "Importa", + "description": "Tasto per importare un account da un file selezionato" + }, + "importAccount": { + "message": "Importa Account" + }, + "importAccountMsg": { + "message":" Gli account importati non saranno associati alla frase seed originariamente creata con MetaMask. Impara di più sugli account importati " + }, + "importAnAccount": { + "message": "Importa un account" + }, + "importDen": { + "message": "Importa un DEN Esistente" + }, + "imported": { + "message": "Importato", + "description": "stato che conferma che un account è stato totalmente caricato nel portachiavi" + }, + "infoHelp": { + "message": "Informazioni & Aiuto" + }, + "insufficientFunds": { + "message": "Fondi non sufficienti." + }, + "insufficientTokens": { + "message": "Token non sufficienti." + }, + "invalidAddress": { + "message": "Indirizzo non valido" + }, + "invalidAddressRecipient": { + "message": "Indirizzo destinatario invalido" + }, + "invalidGasParams": { + "message": "Parametri del Gas non validi" + }, + "invalidInput": { + "message": "Input non valido." + }, + "invalidRequest": { + "message": "Richiesta non Valida" + }, + "invalidRPC": { + "message": "URI RPC invalido" + }, + "jsonFail": { + "message": "Qualcosa è andato storto. Assicurati che il file JSON sia formattato correttamente." + }, + "jsonFile": { + "message": "File JSON", + "description": "formato per importare un account" + }, + "kovan": { + "message": "Rete di test Kovan" + }, + "knowledgeDataBase": { + "message": "Visita la nostra Knowledge Base" + }, + "lessThanMax": { + "message": "deve essere minore o uguale a $1.", + "description": "aiuto per inserire un input esadecimale come decimale" + }, + "likeToAddTokens": { + "message": "Vorresti aggiungere questi token?" + }, + "limit": { + "message": "Limite" + }, + "loading": { + "message": "Caricamento..." + }, + "loadingTokens": { + "message": "Caricamento Tokens..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Connetti" + }, + "logout": { + "message": "Disconnetti" + }, + "loose": { + "message": "Libero" + }, + "loweCaseWords": { + "message": "le frasi seed hanno solo lettere minuscole" + }, + "mainnet": { + "message": "Rete Ethereum Principale" + }, + "message": { + "message": "Messaggio" + }, + "metamaskDescription": { + "message": "MetaMask è una cassaforte sicura per identità su Ethereum." + }, + "min": { + "message": "Minimo" + }, + "myAccounts": { + "message": "Account Miei" + }, + "mustSelectOne": { + "message": "Devi selezionare almeno un token." + }, + "needEtherInWallet": { + "message": "Per interagire con applicazioni decentralizzate con MetaMask, devi possedere Ether nel tuo portafoglio." + }, + "needImportFile": { + "message": "Devi selezionare un file da importare.", + "description": "L'utente sta importando un account e deve aggiungere un file per continuare" + }, + "needImportPassword": { + "message": "Dei inserire una password per il file selezionato.", + "description": "Password e file necessari per importare un account" + }, + "negativeETH": { + "message": "Non puoi inviare una quantità di ETH negativa." + }, + "networks": { + "message": "Reti" + }, + "newAccount": { + "message": "Nuovo Account" + }, + "newAccountNumberName": { + "message": "Account $1", + "description": "Nome predefinito per il prossimo account da creare nella schermata di creazione account" + }, + "newContract": { + "message": "Nuovo Contratto" + }, + "newPassword": { + "message": "Nuova Password (minimo 8 caratteri)" + }, + "newRecipient": { + "message": "Nuovo Destinatario" + }, + "newRPC": { + "message": "Nuovo URL RPC" + }, + "next": { + "message": "Prossimo" + }, + "noAddressForName": { + "message": "Nessun indirizzo è stato impostato per questo nome." + }, + "noDeposits": { + "message": "Nessun deposito ricevuto" + }, + "noTransactionHistory": { + "message": "Nessuna cronologia delle transazioni." + }, + "noTransactions": { + "message": "Nessuna Transazione" + }, + "notStarted": { + "message": "Non Iniziato" + }, + "oldUI": { + "message": "Vecchia interfaccia" + }, + "oldUIMessage": { + "message": "Sei ritornato alla vecchia interfaccia. Puoi ritornare alla nuova interfaccia tramite l'opzione nel menu a discesa in alto a destra." + }, + "or": { + "message": "o", + "description": "scelta tra creare o importare un nuovo account" + }, + "passwordCorrect": { + "message": "Assicurati che la password sia corretta." + }, + "passwordMismatch": { + "message": "le password non corrispondono", + "description": "nella creazione della password, le due password all'interno dei campi non corrispondono" + }, + "passwordShort": { + "message": "password non sufficientemente lunga", + "description": "nella creazione della password, la password non è lunga abbastanza" + }, + "pastePrivateKey": { + "message": "Incolla la tua chiave privata qui:", + "description": "Per importare un account da una chiave privata" + }, + "pasteSeed": { + "message": "Incolla la tua frase seed qui!" + }, + "personalAddressDetected": { + "message": "Rilevato indirizzo personale. Inserisci l'indirizzo del contratto del token." + }, + "pleaseReviewTransaction": { + "message": "Ricontrolla la tua transazione." + }, + "privacyMsg": { + "message": "Politica sulla Privacy" + }, + "privateKey": { + "message": "Chiave Privata", + "description": "seleziona questo tipo di file per importare un account" + }, + "privateKeyWarning": { + "message": "Attenzione: non dire a nessuno questa chiave! Chiunque con la tua chiave privata può rubare qualsiasi moneta contenuta nel tuo account." + }, + "privateNetwork": { + "message": "Rete Privata" + }, + "qrCode": { + "message": "Mostra Codice QR" + }, + "readdToken": { + "message": "Puoi aggiungere nuovamente questo token in futuro andando in “Aggiungi token” nel menu delle opzioni del tuo account." + }, + "readMore": { + "message": "Leggi di più qui." + }, + "readMore2": { + "message": "Leggi di più." + }, + "receive": { + "message": "Ricevi" + }, + "recipientAddress": { + "message": "Indirizzo Destinatario" + }, + "refundAddress": { + "message": "Indirizzo di Rimborso" + }, + "rejected": { + "message": "Respinta" + }, + "resetAccount": { + "message": "Resetta Account" + }, + "restoreFromSeed": { + "message": "Ripristina da una frase seed" + }, + "required": { + "message": "Richiesto" + }, + "retryWithMoreGas": { + "message": "Riprova con un prezzo del Gas maggiore qui" + }, + "revealSeedWords": { + "message": "Rivela Frase Seed" + }, + "revealSeedWordsWarning": { + "message": "Non ripristinare la tua frase seed in pubblico!. Queste parole possono essere usate per rubare il tuo account." + }, + "revert": { + "message": "Annulla" + }, + "rinkeby": { + "message": "Rete di test Rinkeby" + }, + "ropsten": { + "message": "Rete di test Ropsten" + }, + "sampleAccountName": { + "message": "Es: Il mio nuovo account", + "description": "Aiuta l'utente a capire il concetto di aggiungere un nome leggibile al loro account" + }, + "save": { + "message": "Salva" + }, + "saveAsFile": { + "message": "Salva come File", + "description": "Processo per esportare un account" + }, + "saveSeedAsFile": { + "message": "Salva la Frase Seed come File" + }, + "search": { + "message": "Cerca" + }, + "secretPhrase": { + "message": "Inserisci la tua frase segreta di dodici parole per ripristinare la cassaforte." + }, + "seedPhraseReq": { + "message": "le frasi seed sono lunghe 12 parole" + }, + "select": { + "message": "Seleziona" + }, + "selectCurrency": { + "message": "Seleziona Moneta" + }, + "selectService": { + "message": "Seleziona Servizio" + }, + "selectType": { + "message": "Seleziona Tipo" + }, + "send": { + "message": "Invia" + }, + "sendETH": { + "message": "Invia ETH" + }, + "sendTokens": { + "message": "Invia Tokens" + }, + "sendTokensAnywhere": { + "message": "Invia Tokens a chiunque abbia un account Ethereum" + }, + "settings": { + "message": "Impostazioni" + }, + "shapeshiftBuy": { + "message": "Compra con Shapeshift" + }, + "showPrivateKeys": { + "message": "Mostra Chiave Privata" + }, + "showQRCode": { + "message": "Mostra Codie QR" + }, + "sign": { + "message": "Firma" + }, + "signMessage": { + "message": "Firma Messaggio" + }, + "signNotice": { + "message": "Firmare questo messaggio può avere effetti collaterali pericolosi. \nFirma messaggi da siti di cui ti fidi totalmente. \nQuesto metodo pericoloso sarà rimosso in versioni future." + }, + "sigRequest": { + "message": "Firma Richiesta" + }, + "sigRequested": { + "message": "Richiesta Firma" + }, + "spaceBetween": { + "message": "ci può essere solo uno spazio tra le parole" + }, + "status": { + "message": "Stato" + }, + "stateLogs": { + "message": "Log di Stato" + }, + "stateLogsDescription": { + "message": "I log di stato contengono i tuoi indirizzi pubblici e le transazioni effettuate." + }, + "submit": { + "message": "Invia" + }, + "supportCenter": { + "message": "Visita il nostro Centro di Supporto" + }, + "symbolBetweenZeroTen": { + "message": "Il simbolo deve essere lungo tra 0 e 10 caratteri." + }, + "takesTooLong": { + "message": "Ci sta mettendo troppo?" + }, + "terms": { + "message": "Termini di Uso" + }, + "testFaucet": { + "message": "Prova Faucet" + }, + "to": { + "message": "A" + }, + "toETHviaShapeShift": { + "message": "$1 a ETH via ShapeShift", + "description": "il sistema riempirà il tipo di deposito all'inizio del messaggio" + }, + "tokenAddress": { + "message": "Indirizzo Token" + }, + "tokenAlreadyAdded": { + "message": "Il token è già stato aggiunto." + }, + "tokenBalance": { + "message": "Bilancio Token:" + }, + "tokenSelection": { + "message": "Cerca un token o seleziona dalla lista di token più popolari." + }, + "tokenSymbol": { + "message": "Simbolo Token" + }, + "tokenWarning1": { + "message": "Tieni traccia dei token che hai acquistato con il tuo account MetaMask. Se hai acquistato token con un account diverso, quei token non appariranno qui." + }, + "total": { + "message": "Totale" + }, + "transactions": { + "message": "transazioni" + }, + "transactionMemo": { + "message": "Promemoria Transazione (opzionale)" + }, + "transactionNumber": { + "message": "Numero Transazione" + }, + "transfers": { + "message": "Trasferimenti" + }, + "troubleTokenBalances": { + "message": "Abbiamo avuto un problema a caricare il bilancio dei tuoi token. Puoi vederlo ", + "description": "Seguito da un link (qui) per vedere il bilancio dei token" + }, + "twelveWords": { + "message": "Queste 12 parole sono l'unico modo per ripristinare i tuoi account MetaMask. \nSalvale in un posto sicuro e segreto." + }, + "typePassword": { + "message": "Inserisci Password" + }, + "uiWelcome": { + "message": "Benvenuto alla nuova interfaccia (Beta)" + }, + "uiWelcomeMessage": { + "message": "Stai utilizzanto la nuova interfaccia di MetaMask. Guarda in giro, prova nuove funzionalità come inviare token, e facci sapere se hai dei problemi." + }, + "unavailable": { + "message": "Non Disponibile" + }, + "unknown": { + "message": "Sconosciuto" + }, + "unknownNetwork": { + "message": "Rete Privata Sconosciuta" + }, + "unknownNetworkId": { + "message": "ID rete sconosciuto" + }, + "uriErrorMsg": { + "message": "Gli URI richiedono un prefisso HTTP/HTTPS." + }, + "usaOnly": { + "message": "Solo USA", + "description": "Usare questo sito di scambio è possibile solo per persone residenti in USA." + }, + "usedByClients": { + "message": "Usato da una varietà di clients diversi" + }, + "useOldUI": { + "message": "Use la vecchia UI" + }, + "validFileImport": { + "message": "Devi selezionare un file valido da importare." + }, + "vaultCreated": { + "message": "Cassaforte Creata" + }, + "viewAccount": { + "message": "Vedi Account" + }, + "visitWebSite": { + "message": "Visita il nostro sito web" + }, + "warning": { + "message": "Attenzione" + }, + "welcomeBeta": { + "message": "Benvenuto nella Beta di MetaMask" + }, + "whatsThis": { + "message": "Cos'è questo?" + }, + "yourSigRequested": { + "message": "E' richiesta la tua firma" + }, + "youSign": { + "message": "Ti stai connettendo" + } +}
\ No newline at end of file diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json new file mode 100644 index 000000000..29d63be02 --- /dev/null +++ b/app/_locales/ph/messages.json @@ -0,0 +1,609 @@ +{ + "accept": { + "message": "Tanggapin" + }, + "account": { + "message": "Account" + }, + "accountDetails": { + "message": "Detalye ng Account" + }, + "accountName": { + "message": "Pangalan ng Account" + }, + "address": { + "message": "Address" + }, + "addToken": { + "message": "Magdagdag ng Token" + }, + "amount": { + "message": "Halaga" + }, + "amountPlusGas": { + "message": "Halaga + Gas" + }, + "appDescription": { + "message": "Ethereum Browser Extension", + "description": "Ang deskripsyon ng application" + }, + "appName": { + "message": "MetaMask", + "description": "Ang pangalan ng application" + }, + "attemptingConnect": { + "message": "Sinusubukang kumonekta sa blockchain." + }, + "available": { + "message": "Magagamit" + }, + "back": { + "message": "Bumalik" + }, + "balance": { + "message": "Balanse:" + }, + "balanceIsInsufficientGas": { + "message": "Kulang ang balanse para sa kasalukuyang gas total" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "dapat mas malaki o katumbas ng $1 at mas mababa o katumbas ng $2.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, + "borrowDharma": { + "message": "Humiram sa Dharma (Beta)" + }, + "buy": { + "message": "Bumili" + }, + "buyCoinbase": { + "message": "Bumili sa Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Ang Coinbase ang pinakasikat na paraan upang bumili at magbenta ng bitcoin, ethereum, at litecoin sa buong mundo." + }, + "cancel": { + "message": "Kanselahin" + }, + "clickCopy": { + "message": "I-click upang Makopya" + }, + "confirm": { + "message": "Tiyakin" + }, + "confirmContract": { + "message": "Tiyakin ang Contract" + }, + "confirmPassword": { + "message": "Tiyakin ang Password" + }, + "confirmTransaction": { + "message": "Tiyakin ang Transaksyon" + }, + "continueToCoinbase": { + "message": "Magpatuloy sa Coinbase" + }, + "contractDeployment": { + "message": "Pag-deploy ng Contract" + }, + "conversionProgress": { + "message": "Isinasagawa ang conversion" + }, + "copiedButton": { + "message": "Kinopya" + }, + "copiedClipboard": { + "message": "Kinopya sa Clipboard" + }, + "copiedExclamation": { + "message": "Kinopya!" + }, + "copy": { + "message": "Kinopya" + }, + "copyToClipboard": { + "message": "Kinopya sa clipboard" + }, + "copyButton": { + "message": " Kinopya " + }, + "copyPrivateKey": { + "message": "Ito ang iyong private key (i-click upang makopya)" + }, + "create": { + "message": "Gumawa" + }, + "createAccount": { + "message": "Gumawa ng Account" + }, + "createDen": { + "message": "Gumawa" + }, + "crypto": { + "message": "Crypto", + "description": "Type ng exchange (cryptocurrencies)" + }, + "customGas": { + "message": "I-customize ang Gas" + }, + "customize": { + "message": "I-customize" + }, + "customRPC": { + "message": "Custom RPC" + }, + "defaultNetwork": { + "message": "Ang default network para sa Ether transactions ay ang Main Net." + }, + "denExplainer": { + "message": "Ang iyong DEN ang nagsisilbing password-encrypted storage mo sa loob ng MetaMask." + }, + "deposit": { + "message": "Deposito" + }, + "depositBTC": { + "message": "I-deposito ang iyong BTC sa address na ito:" + }, + "depositCoin": { + "message": "I-deposito ang iyong $1 sa address na ito", + "description": "Sinasabihan ang user kung ano ang coin na kanilang pinili para I-deposito gamit ang shapeshift" + }, + "depositEth": { + "message": "I-deposito ang Eth" + }, + "depositEther": { + "message": "I-deposito ang Ether" + }, + "depositFiat": { + "message": "I-deposito ang Fiat" + }, + "depositFromAccount": { + "message": "I-deposito mula sa ibang account" + }, + "depositShapeShift": { + "message": "I-deposito gamit ang ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Kung ikaw ay nagmamay-ari ng iba pang cryptocurrencies, pwede kang mag-trade at mag-deposito ng Ether diretso sa iyong MetaMask wallet. Hindi mo na kailangan ng account." + }, + "details": { + "message": "Detalye" + }, + "directDeposit": { + "message": "Direktang Deposito" + }, + "directDepositEther": { + "message": "Direktang I-deposito ang Ether" + }, + "directDepositEtherExplainer": { + "message": "Kung ika ay mayroon nang Ether, ang pinakamabilis na paraan upang makuha ang Ether sa iyong bagong wallet ay sa pamamagitan ng direktang deposito." + }, + "done": { + "message": "Tapos na" + }, + "edit": { + "message": "I-edit" + }, + "editAccountName": { + "message": "I-edit ang Pangalang ng Account" + }, + "encryptNewDen": { + "message": "I-encrypt ang iyong bagong DEN" + }, + "enterPassword": { + "message": "I-enter ang password" + }, + "etherscanView": { + "message": "Tingnan ang account sa Etherscan" + }, + "exchangeRate": { + "message": "Exchange Rate" + }, + "exportPrivateKey": { + "message": "I-export ang Private Key" + }, + "exportPrivateKeyWarning": { + "message": "I-export ang private keys at intindihin ang panganib na kasama nito." + }, + "failed": { + "message": "Nabigo" + }, + "fiat": { + "message": "FIAT", + "description": "Type ng exchange" + }, + "fileImportFail": { + "message": "Hindi gumagana ang file import? I-click ito!", + "description": "Tinutulungan ang user na i-import ang kanilang account mula sa JSON file" + }, + "from": { + "message": "Mula sa" + }, + "fromShapeShift": { + "message": "Mula sa ShapeShift" + }, + "gas": { + "message": "Gas", + "description": "Maikling indikasyon ng gas cost" + }, + "gasFee": { + "message": "Gas Fee" + }, + "gasLimit": { + "message": "Gas Limit" + }, + "gasLimitCalculation": { + "message": "Kinalkula namin ang iminungkahing gas limit base sa network success rates." + }, + "gasLimitRequired": { + "message": "Kailangan ang Gas Limit" + }, + "gasLimitTooLow": { + "message": "Ang gas limit ay hindi dabat bababa sa 21000" + }, + "gasPrice": { + "message": "Gas Price (GWEI)" + }, + "gasPriceCalculation": { + "message": "Kinalkula namin ang iminungkahing gas prices base sa network success rates." + }, + "gasPriceRequired": { + "message": "Kailangan ang Gas Price" + }, + "getEther": { + "message": "Kumuha ng Ether" + }, + "getEtherFromFaucet": { + "message": "Kumuha ng Ether mula sa faucet para sa $1", + "description": "Ipinapakita ang pangalan ng network para sa Ether faucet" + }, + "greaterThanMin": { + "message": "dapat mas malaki o katumbas ng $1.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, + "here": { + "message": "i-click ito", + "description": "tulad ng -i-click dito- para sa mas maraming impormasyon (kasama ng troubleTokenBalances)" + }, + "hide": { + "message": "Itago" + }, + "hideToken": { + "message": "Itago ang Token" + }, + "hideTokenPrompt": { + "message": "Itago ang Token?" + }, + "howToDeposit": { + "message": "Paano mo gustong mag-deposito ng Ether?" + }, + "import": { + "message": "I-import", + "description": "Button para i-import ang account mula sa napiling file" + }, + "importAccount": { + "message": "I-import ang Account" + }, + "importAnAccount": { + "message": "I-import ang account" + }, + "importDen": { + "message": "I-import ang Existing DEN" + }, + "imported": { + "message": "Na-import na", + "description": "status na nagpapakita na ang account ay lubos na na-load sa keyring" + }, + "infoHelp": { + "message": "Impormasyon at Tulong" + }, + "invalidAddress": { + "message": "Invalid ang address" + }, + "invalidGasParams": { + "message": "Invalid ang Gas Parameters" + }, + "invalidInput": { + "message": "Invalid ang input." + }, + "invalidRequest": { + "message": "Invalid ang Request" + }, + "jsonFile": { + "message": "JSON File", + "description": "format para sa pag-import ng account" + }, + "kovan": { + "message": "Kovan Test Network" + }, + "lessThanMax": { + "message": "dapat mas mababa o katumbas ng $1.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, + "limit": { + "message": "Limitasyon" + }, + "loading": { + "message": "Naglo-load..." + }, + "loadingTokens": { + "message": "Naglo-load ang Tokens..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "logout": { + "message": "Log out" + }, + "loose": { + "message": "Loose" + }, + "mainnet": { + "message": "Main Ethereum Network" + }, + "message": { + "message": "Mensahe" + }, + "min": { + "message": "Minimum" + }, + "myAccounts": { + "message": "Aking mga Account" + }, + "needEtherInWallet": { + "message": "Upang makipag-ugnayan sa decentralized applications gamit ang MetaMask, kakailanganin mo ng Ether sa iyong wallet." + }, + "needImportFile": { + "message": "Dapat kang pumili ng file para i-import.", + "description": "Ang user ay nag-iimport ng account at kailangan magdagdag ng file upang tumuloy" + }, + "needImportPassword": { + "message": "Dapat mong i-enter ang password para sa napiling file.", + "description": "Password at file na kailangan upang ma-import ang account" + }, + "networks": { + "message": "Networks" + }, + "newAccount": { + "message": "Bagong Account" + }, + "newAccountNumberName": { + "message": "Account $1", + "description": "Ang default na pangalan ng susunod na account na gagawin sa create account screen" + }, + "newContract": { + "message": "Bagong Contract" + }, + "newPassword": { + "message": "Bagong Password (min 8 chars)" + }, + "newRecipient": { + "message": "Bagong Recipient" + }, + "next": { + "message": "Sunod" + }, + "noAddressForName": { + "message": "Walang naka-set na address para sa pangalang ito." + }, + "noDeposits": { + "message": "Walang natanggap na mga deposito" + }, + "noTransactionHistory": { + "message": "Walang kasaysayan ng transaksyon." + }, + "noTransactions": { + "message": "Walang mga Transaksyon" + }, + "notStarted": { + "message": "Hindi Sinimulan" + }, + "oldUI": { + "message": "Lumang UI" + }, + "oldUIMessage": { + "message": "Ikaw ay bumalik sa lumang UI. Maaari kang bumalik sa bagong UI mula sa isang opsyon sa dropdown menu na matatagpuan sa bandang taas at kanan." + }, + "or": { + "message": "o", + "description": "Pagpili sa pagitan ng paggawa of pag-import ng bagong account" + }, + "passwordMismatch": { + "message": "hindi nagtugma ang mga password", + "description": "Sa proseso ng paggawa ng password, ang dalawang password fields ay hindi nagtugma" + }, + "passwordShort": { + "message": "hindi sapat ang haba ng password", + "description": "Sa proseso ng paggawa ng password, ang password ay hindi in password creation process, hind sapat ang haba ng password upang maging ligtas" + }, + "pastePrivateKey": { + "message": "I-paste dito ang iyong private key string:", + "description": "Para sa pag-import ng account mula sa private key" + }, + "pasteSeed": { + "message": "I-paste dito ang iyong seed phrase!" + }, + "pleaseReviewTransaction": { + "message": "Mangyaring suriin ang iyong transaksyon." + }, + "privateKey": { + "message": "Private Key", + "description": "Piliin ang ganitong type ng file upang gamitin sa pag-import ng account" + }, + "privateKeyWarning": { + "message": "Babala: Huwag sabihin sa kahit na sino ang key na ito. Maaring makuha at manakaw ng sinumang nakakaalam ng iyong private key ang mga assets sa iyong account." + }, + "privateNetwork": { + "message": "Pribadong Network" + }, + "qrCode": { + "message": "Ipakita ang QR Code" + }, + "readdToken": { + "message": "Upang muling idagdag ang token na ito, pumunta sa “Magdagdag ng Token” sa options menu ng iyong account." + }, + "readMore": { + "message": "Alamin ang iba pang impormasyon dito." + }, + "receive": { + "message": "Tanggapin" + }, + "recipientAddress": { + "message": "Address ng Tatanggap" + }, + "refundAddress": { + "message": "Ang Iyong Refund Address" + }, + "rejected": { + "message": "Tinanggihan" + }, + "required": { + "message": "Kailangan" + }, + "retryWithMoreGas": { + "message": "Muling subukan ng may mas mataas na gas price dito" + }, + "revert": { + "message": "Ibalik" + }, + "rinkeby": { + "message": "Rinkeby Test Network" + }, + "ropsten": { + "message": "Ropsten Test Network" + }, + "sampleAccountName": { + "message": "Halimbawa: Ang aking bagong account", + "description": "Tulungan ang user na intindihin ang konsepto ng pagdagdag ng human-readable name sa kanilang account" + }, + "save": { + "message": "I-save" + }, + "saveAsFile": { + "message": "I-save bilang File", + "description": "Proseso sa pag-export ng Account" + }, + "selectService": { + "message": "Piliin ang Service" + }, + "send": { + "message": "Magpadala" + }, + "sendTokens": { + "message": "Magpadala ng Tokens" + }, + "sendTokensAnywhere": { + "message": "Magpadala ng Tokens sa sinumang may Ethereum account" + }, + "settings": { + "message": "Mga Setting" + }, + "shapeshiftBuy": { + "message": "Bumili gamit ang Shapeshift" + }, + "showPrivateKeys": { + "message": "Ipakita ang Private Keys" + }, + "showQRCode": { + "message": "Ipakita ang QR Code" + }, + "sign": { + "message": "I-sign" + }, + "signMessage": { + "message": "I-sign ang mensahe" + }, + "signNotice": { + "message": "Ang pag-sign ng mensaheng ito ay maaring magdulot ng mapanganib na epekto. I-sign lamang ang mga mensahe mula sa mga site na pinagkakatiwalaan mo ng iyong account. Ang mapanganib na paraang ito ay aalisin sa isa sa mga susunod na bersyon. " + }, + "sigRequest": { + "message": "Hiling na Signature" + }, + "sigRequested": { + "message": "Hiniling ang Signature" + }, + "status": { + "message": "Istado" + }, + "submit": { + "message": "I-submit" + }, + "takesTooLong": { + "message": "Masyadong matagal?" + }, + "testFaucet": { + "message": "Test Faucet" + }, + "to": { + "message": "To" + }, + "toETHviaShapeShift": { + "message": "$1 sa ETH sa pamamagitan ng ShapeShift", + "description": "Pupunan ng system ang deposit type sa simula ng mensahe" + }, + "tokenBalance": { + "message": "Ang iyong Token Balance ay:" + }, + "total": { + "message": "Kabuuan" + }, + "transactionMemo": { + "message": "Memo ng transaksyon (opsyonal)" + }, + "transactionNumber": { + "message": "Numero ng Transaksyon" + }, + "transfers": { + "message": "Mga Inilipat" + }, + "troubleTokenBalances": { + "message": "Nagkaroon kami ng problema sa paglo-load ng iyong mga balanseng token. Tingnan ito dito ", + "description": "Susundan ng link (dito) para tingnan ang token balances" + }, + "typePassword": { + "message": "I-type ang iyong Password" + }, + "uiWelcome": { + "message": "Maligayang pagdating sa Bagong UI (Beta)" + }, + "uiWelcomeMessage": { + "message": "Ginagamit mo na ngayon ang bagong MetaMask UI. I-explore at subukan ang mga bagong features tulad ng pagpapadala ng mga token, at ipaalam sa amin kung mayroon kang anumang mga isyu." + }, + "unavailable": { + "message": "Hindi Magagamit" + }, + "unknown": { + "message": "Hindi Alam" + }, + "unknownNetwork": { + "message": "Hindi Alam ang Pribadong Network" + }, + "unknownNetworkId": { + "message": "Hindi alam ang network ID" + }, + "usaOnly": { + "message": "USA lamang", + "description": "Ang paggamit ng exchange na ito ay limitado sa mga tao sa loob ng Estados Unidos" + }, + "usedByClients": { + "message": "Ginagamit ng iba't ibang mga clients" + }, + "viewAccount": { + "message": "Tingnan ang Account" + }, + "warning": { + "message": "Babala" + }, + "whatsThis": { + "message": "Ano ito?" + }, + "yourSigRequested": { + "message": "Hinihiling ang iyong signature" + }, + "youSign": { + "message": "Ikaw ay nagsa-sign" + } +} diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json new file mode 100644 index 000000000..c9eb178f9 --- /dev/null +++ b/app/_locales/pt/messages.json @@ -0,0 +1,819 @@ +{ + "accept": { + "message": "Aceitar" + }, + "account": { + "message": "Conta" + }, + "accountDetails": { + "message": "Detalhes da Conta" + }, + "accountName": { + "message": "Nome da Conta" + }, + "address": { + "message": "Endereço" + }, + "addCustomToken": { + "message": "Adicionar token customizada" + }, + "addToken": { + "message": "Adicionar Token" + }, + "addTokens": { + "message": "Adicionar Tokens" + }, + "amount": { + "message": "Valor" + }, + "amountPlusGas": { + "message": "Valor + Gas" + }, + "appDescription": { + "message": "Extensão para o browser de Ethereum", + "description": "A descrição da aplicação" + }, + "appName": { + "message": "MetaMask", + "description": "Nome da aplicação" + }, + "attemptingConnect": { + "message": "A tentar ligar à blockchain." + }, + "attributions": { + "message": "Atribuições" + }, + "available": { + "message": "Disponível" + }, + "back": { + "message": "Voltar" + }, + "balance": { + "message": "Saldo:" + }, + "balances": { + "message": "O meu saldo" + }, + "balanceIsInsufficientGas": { + "message": "Saldo insuficiente para a quantidade de gas total" + }, + "beta": { + "message": "BETA" + }, + "betweenMinAndMax": { + "message": "tem de ser maior ou igual a $1 e menor ou igual a $2.", + "description": "ajuda para introduzir hexadecimal como decimal" + }, + "blockiesIdenticon": { + "message": "Usar Blockies Identicon" + }, + "borrowDharma": { + "message": "Pedir Empréstimo Com Dharma (Beta)" + }, + "builtInCalifornia": { + "message": "MetaMask é desenhada e construída na California." + }, + "buy": { + "message": "Comprar" + }, + "buyCoinbase": { + "message": "Comprar no Coinbase" + }, + "buyCoinbaseExplainer": { + "message": "Coinbase é a forma mais conhecida para comprar e vender bitcoin, ethereum, e litecoin." + }, + "cancel": { + "message": "Cancelar" + }, + "classicInterface": { + "message": "Utilizar interface clássico" + }, + "clickCopy": { + "message": "Carregue para copiar" + }, + "confirm": { + "message": "Confirmar" + }, + "confirmContract": { + "message": "Confirmar Contrato" + }, + "confirmPassword": { + "message": "Confirmar Palavra-passe" + }, + "confirmTransaction": { + "message": "Confirmar Transação" + }, + "continue": { + "message": "Continuar" + }, + "continueToCoinbase": { + "message": "Continuar para o Coinbase" + }, + "contractDeployment": { + "message": "Distribuição do Contrato" + }, + "conversionProgress": { + "message": "Conversão em progresso" + }, + "copiedButton": { + "message": "Copiado" + }, + "copiedClipboard": { + "message": "Copiado para a Área de Transferência" + }, + "copiedExclamation": { + "message": "Copiado!" + }, + "copiedSafe": { + "message": "Já copiei para um lugar seguro" + }, + "copy": { + "message": "Copiar" + }, + "copyToClipboard": { + "message": "Copiar para o clipboard" + }, + "copyButton": { + "message": " Copiar " + }, + "copyPrivateKey": { + "message": "Esta é a sua chave privada (carregue para copiar)" + }, + "create": { + "message": "Criar" + }, + "createAccount": { + "message": "Criar Conta" + }, + "createDen": { + "message": "Criar" + }, + "crypto": { + "message": "Cripto", + "description": "Tipo de câmbio (criptomoedas)" + }, + "currentConversion": { + "message": "Taxa de Conversão Atual" + }, + "currentNetwork": { + "message": "Rede Atual" + }, + "customGas": { + "message": "Customizar Gas" + }, + "customize": { + "message": "Customizar" + }, + "customRPC": { + "message": "Customizar RPC" + }, + "decimalsMustZerotoTen": { + "message": "Decimais devem ser no mínimo 0 e não passar de 36." + }, + "decimal": { + "message": "Precisão em Decimais" + }, + "defaultNetwork": { + "message": "A rede pré definida para transações em Ether é a Main Net." + }, + "denExplainer": { + "message": " DEN é o armazenamento encriptado da sua palavra-passe no MetaMask." + }, + "deposit": { + "message": "Depósito" + }, + "depositBTC": { + "message": "Deposite as suas BTC no endereço abaixo:" + }, + "depositCoin": { + "message": "Deposite $1 no endereço abaixo", + "description": "Diz ao usuário que moeda selecionou para depositar com shapeshift" + }, + "depositEth": { + "message": "Depositar Eth" + }, + "depositEther": { + "message": "Depositar Ether" + }, + "depositFiat": { + "message": "Depositar moeda fiduciária" + }, + "depositFromAccount": { + "message": "Depositar de outra conta" + }, + "depositShapeShift": { + "message": "Depositar com ShapeShift" + }, + "depositShapeShiftExplainer": { + "message": "Se tem criptomoedas, pode trocar e depositar Ether diretamente na sua carteira MetaMask. Não precisa de conta." + }, + "details": { + "message": "Detalhes" + }, + "directDeposit": { + "message": "Depósito Direto" + }, + "directDepositEther": { + "message": "Depositar Diretamente Ether" + }, + "directDepositEtherExplainer": { + "message": "Se já tem Ether, a forma mais rápida de ficar com Ether na sua carteira é através de depósito direto." + }, + "done": { + "message": "Finalizado" + }, + "downloadStatelogs": { + "message": "Descarregar Registos de Estado" + }, + "edit": { + "message": "Editar" + }, + "editAccountName": { + "message": "Editar Nome da Conta" + }, + "emailUs": { + "message": "Fale connosco!" + }, + "encryptNewDen": { + "message": "Encripte o seu novo DEN" + }, + "enterPassword": { + "message": "Introduza palavra-passe" + }, + "enterPasswordConfirm": { + "message": "Introduza a sua palavra-passe para confirmar" + }, + "etherscanView": { + "message": "Ver conta no Etherscan" + }, + "exchangeRate": { + "message": "Taxa de Câmbio" + }, + "exportPrivateKey": { + "message": "Exportar Chave Privada" + }, + "exportPrivateKeyWarning": { + "message": "Exportar chaves privadas por sua conta e risco." + }, + "failed": { + "message": "Falhou" + }, + "fiat": { + "message": "FIAT", + "description": "Tipo de câmbio" + }, + "fileImportFail": { + "message": "A importação de ficheiro não está a funcionar? Carregue aqui!", + "description": "Ajuda usuários a importar as suas contas a partir de um ficheiro JSON" + }, + "followTwitter": { + "message": "Siga-nos no Twitter" + }, + "from": { + "message": "De" + }, + "fromToSame": { + "message": "Endereços De e Para não podem ser iguais" + }, + "fromShapeShift": { + "message": "De ShapeShift" + }, + "gas": { + "message": "Gas", + "description": "Indicação breve do custo de gas" + }, + "gasFee": { + "message": "Taxa de Gas" + }, + "gasLimit": { + "message": "Limite de Gas" + }, + "gasLimitCalculation": { + "message": "Calculamos o limite sugerido do gas com base nas taxas de sucesso da rede." + }, + "gasLimitRequired": { + "message": "Limite de Gas Necessário" + }, + "gasLimitTooLow": { + "message": "Limite de Gas deve ser no mínimo 21000" + }, + "generatingSeed": { + "message": "A gerar Seed..." + }, + "gasPrice": { + "message": "Preço Gas (GWEI)" + }, + "gasPriceCalculation": { + "message": "Calculamos o gas sugerido com base nas taxas de sucesso da rede." + }, + "gasPriceRequired": { + "message": "Preço Gas Necessário" + }, + "getEther": { + "message": "Obter Ether" + }, + "getEtherFromFaucet": { + "message": "Obter Ether de um faucet por $1", + "description": "Mostra nome da rede para faucet de Ether" + }, + "greaterThanMin": { + "message": "tem de ser maior ou igual a $1.", + "description": "ajuda para introduzir hexadecimal como decimal" + }, + "here": { + "message": "aqui", + "description": "como -clicar aqui- para mais informações (associado a troubleTokenBalances)" + }, + "hereList": { + "message": "Aqui está uma lista!!!!" + }, + "hide": { + "message": "Ocultar" + }, + "hideToken": { + "message": "Ocultar Token" + }, + "hideTokenPrompt": { + "message": "Ocultar Token?" + }, + "howToDeposit": { + "message": "Como gostaria de depositar Ether?" + }, + "holdEther": { + "message": "Permite ter ether & tokens, e serve como uma ponte para aplicações descentralizadas." + }, + "import": { + "message": "Importar", + "description": "Botão para importar uma conta de um ficheiro selecionado" + }, + "importAccount": { + "message": "Importar Conta" + }, + "importAccountMsg": { + "message":"Contas importadas não irão ser associadas com a frase seed da conta criada originalmente pelo MetaMask. Saiba mais sobre contas importadas." + }, + "importAnAccount": { + "message": "Importar uma conta" + }, + "importDen": { + "message": "Importar DEN Existente" + }, + "imported": { + "message": "Importado", + "description": "estado para mostrar que uma conta foi totalmente carregada para o keyring" + }, + "infoHelp": { + "message": "Informação & Ajuda" + }, + "insufficientFunds": { + "message": "Fundos insuficientes." + }, + "insufficientTokens": { + "message": "Tokens insuficientes." + }, + "invalidAddress": { + "message": "Endereço inválido" + }, + "invalidAddressRecipient": { + "message": "O endereço do destinatário é inválido " + }, + "invalidGasParams": { + "message": "Parâmetros para o Gas Inválidos" + }, + "invalidInput": { + "message": "Campo inválido." + }, + "invalidRequest": { + "message": "Pedido Inválido" + }, + "invalidRPC": { + "message": "RPC URI Inválido" + }, + "jsonFail": { + "message": "Ocorreu um erro. Por favor confirme que o seu ficheiro JSON está devidamente formatado." + }, + "jsonFile": { + "message": "Ficheiro JSON", + "description": "Formatar para importar uma conta" + }, + "kovan": { + "message": "Rede de Teste Kovan" + }, + "knowledgeDataBase": { + "message": "Visite o nosso Centro de Conhecimento" + }, + "lessThanMax": { + "message": "tem de ser menor ou igual a $1.", + "description": "ajuda para introduzir hexadecimal como decimal" + }, + "likeToAddTokens": { + "message": "Gostaria de adicionar estes tokens?" + }, + "limit": { + "message": "Limite" + }, + "loading": { + "message": "A carregar..." + }, + "loadingTokens": { + "message": "A carregar Tokens..." + }, + "localhost": { + "message": "Localhost 8545" + }, + "login": { + "message": "Entrar" + }, + "logout": { + "message": "Sair" + }, + "loose": { + "message": "Vago" + }, + "loweCaseWords": { + "message": "palavras da seed apenas têm caracteres minúsculos" + }, + "mainnet": { + "message": "Rede Principal de Ethereum" + }, + "message": { + "message": "Mensagem" + }, + "metamaskDescription": { + "message": "O MetaMask é um lugar seguro para guardar a sua identidade em em Ethereum." + }, + "min": { + "message": "Mínimo" + }, + "myAccounts": { + "message": "As minhas contas" + }, + "mustSelectOne": { + "message": "Deve escolher no mínimo 1 token." + }, + "needEtherInWallet": { + "message": "Para interagir com applicações descentralizadas usando MetaMask tem de ter Ether na sua carteira." + }, + "needImportFile": { + "message": "Deve selecionar um ficheiro para importar.", + "description": "O utilizador deve adicionar um ficheiro para continuar" + }, + "needImportPassword": { + "message": "Deve introduzir uma palavra-passe para o ficheiro selecionado.", + "description": "Palavra-passe e ficheiro necessários para importar uma conta" + }, + "negativeETH": { + "message": "Não é possível enviar valores negativos de ETH." + }, + "networks": { + "message": "Redes" + }, + "newAccount": { + "message": "Conta Nova" + }, + "newAccountNumberName": { + "message": "Conta $1", + "description": "Nome padrão da próxima conta a ser criado em Criar Conta" + }, + "newContract": { + "message": "Contrato Novo" + }, + "newPassword": { + "message": "Nova Palavra-passe (min 8 caracteres)" + }, + "newRecipient": { + "message": "Recipiente Novo" + }, + "newRPC": { + "message": "Novo RPC URL" + }, + "next": { + "message": "Próximo" + }, + "noAddressForName": { + "message": "Nenhum endereço foi estabelecido para este nome." + }, + "noDeposits": { + "message": "Sem depósitos recebidos" + }, + "noTransactionHistory": { + "message": "Sem histórico de transações." + }, + "noTransactions": { + "message": "Sem Transações" + }, + "notStarted": { + "message": "Não Iniciado" + }, + "oldUI": { + "message": "UI Antigo" + }, + "oldUIMessage": { + "message": "Voltou para o UI antigo. Pode reverter para o Novo UI através da opção no menu do topo direito." + }, + "or": { + "message": "ou", + "description": "opção entre criar ou importar uma nova conta" + }, + "passwordCorrect": { + "message": "Por favor confirme que a sua palavra-passe esteja correta." + }, + "passwordMismatch": { + "message": "as palavras-passe não coincidem", + "description": "no processo de criação da palavra-passe, as duas palavras-passe não coincidiram" + }, + "passwordShort": { + "message": "palavra-passe deve ser mais comprida", + "description": "no processo de criação da palavra-passe, a palavra-apasse não é longa o suficiente para ser segura" + }, + "pastePrivateKey": { + "message": "Cole aqui a sua chave privada:", + "description": "Para importar uma conta através da chave privada" + }, + "pasteSeed": { + "message": "Cole aqui a sua frase seed!" + }, + "personalAddressDetected": { + "message": "Endereço pessoal detectado. Introduza o endereço do contrato do token." + }, + "pleaseReviewTransaction": { + "message": "Por favor reveja a sua transação." + }, + "privacyMsg": { + "message": "Política de Privacidade" + }, + "privateKey": { + "message": "Chave Privada", + "description": "Selecione este tipo de ficheiro para importar uma conta" + }, + "privateKeyWarning": { + "message": "Atenção: Nunca revele esta chave. Qualquer pessoa com acesso à sua chave privada pode roubar os bens que esta contém." + }, + "privateNetwork": { + "message": "Rede Privada" + }, + "qrCode": { + "message": "Mostrar Código QR" + }, + "readdToken": { + "message": "Pode adicionar este token de novo clicando na opção “Adicionar token” no menu de opções da sua conta." + }, + "readMore": { + "message": "Ler mais aqui." + }, + "readMore2": { + "message": "Ler mais." + }, + "receive": { + "message": "Receber" + }, + "recipientAddress": { + "message": "Endereço do Destinatário" + }, + "refundAddress": { + "message": "O seu endereço de reembolso" + }, + "rejected": { + "message": "Rejeitado" + }, + "resetAccount": { + "message": "Reinicializar Conta" + }, + "restoreFromSeed": { + "message": "Restaurar a partir da frase seed" + }, + "required": { + "message": "Necessário" + }, + "retryWithMoreGas": { + "message": "Tentar novamente com um preço mais elevado aqui" + }, + "revealSeedWords": { + "message": "Revelar Palavras Seed" + }, + "revealSeedWordsWarning": { + "message": "Não revele as palavras seed num espaço público! Estas palavras podem ser usadas para roubar todas as suas contas." + }, + "revert": { + "message": "Reverter" + }, + "rinkeby": { + "message": "Rede de Teste Rinkeby" + }, + "ropsten": { + "message": "Rede de Teste Ropsten" + }, + "sampleAccountName": { + "message": "Ex. A minha conta nova", + "description": "Ajuda o utilizador a perceber o conceito de adicionar um nome legível à sua conta" + }, + "save": { + "message": "Guardar" + }, + "saveAsFile": { + "message": "Guardar como Ficheiro", + "description": "Processo de exportação de conta" + }, + "saveSeedAsFile": { + "message": "Guardar Palavras Seed como um Ficheiro" + }, + "search": { + "message": "Procurar" + }, + "secretPhrase": { + "message": "Introduza a sua frase secreta de 12 palavras para recuperar o seu ." + }, + "seedPhraseReq": { + "message": "seed phrases are 12 words long" + }, + "select": { + "message": "Selecionar" + }, + "selectCurrency": { + "message": "Selecionar Moeda" + }, + "selectService": { + "message": "Selecionar Serviço" + }, + "selectType": { + "message": "Selecionar Tipo" + }, + "send": { + "message": "Enviar" + }, + "sendETH": { + "message": "Enviar ETH" + }, + "sendTokens": { + "message": "Enviar Tokens" + }, + "sendTokensAnywhere": { + "message": "Enviar Tokens para qualquer pessoa com uma conta Ethereum" + }, + "settings": { + "message": "Definições" + }, + "shapeshiftBuy": { + "message": "Comprar com Shapeshift" + }, + "showPrivateKeys": { + "message": "Mostrar Chaves Privadas" + }, + "showQRCode": { + "message": "Mostrar Código QR" + }, + "sign": { + "message": "Assinar" + }, + "signMessage": { + "message": "Assinar Mensagem" + }, + "signNotice": { + "message": "Assinar esta mensagem pode ter \nefeitos laterais perigosos. Apenas assine mensagens de sites que \ntotalmente confia com a sua conta total.\n Este método perigoso será removido numa versão posterior." + }, + "sigRequest": { + "message": "Pedido de Assinatura" + }, + "sigRequested": { + "message": "Assinatura Pedida" + }, + "spaceBetween": { + "message": "só pode haver um espaço entre palavras" + }, + "status": { + "message": "Estado" + }, + "stateLogs": { + "message": "Registos de Estado" + }, + "stateLogsDescription": { + "message": "Registo de estado podem conter o seu endereço e transações enviadas da sua conta pública." + }, + "submit": { + "message": "Submeter" + }, + "supportCenter": { + "message": "Visitar o nosso Centro de Suporte" + }, + "symbolBetweenZeroTen": { + "message": "Símbolo deve conter entre 0 e 10 characters." + }, + "takesTooLong": { + "message": "A demorar muito?" + }, + "terms": { + "message": "Termos de Uso" + }, + "testFaucet": { + "message": "Faucet de Teste" + }, + "to": { + "message": "Para" + }, + "toETHviaShapeShift": { + "message": "$1 para ETH via ShapeShift", + "description": "o sistema irá preencher o tipo de depósito no início da mensagem" + }, + "tokenAddress": { + "message": "Endereço do Token" + }, + "tokenAlreadyAdded": { + "message": "Token já foi adicionado." + }, + "tokenBalance": { + "message": "O seu balanço é:" + }, + "tokenSelection": { + "message": "Procure por tokens ou seleccione da nossa lista de tokens populares." + }, + "tokenSymbol": { + "message": "Símbolo do Token" + }, + "tokenWarning1": { + "message": "Registe os tokens que comprou com a sua conta MetaMask. Se comprou tokens utilizando uma conta diferente, esses tokens não irão aparecer aqui." + }, + "total": { + "message": "Total" + }, + "transactions": { + "message": "transações" + }, + "transactionMemo": { + "message": "Notas da transação (opcional)" + }, + "transactionNumber": { + "message": "Número da Transação" + }, + "transfers": { + "message": "Transferências" + }, + "troubleTokenBalances": { + "message": "Tivemos um problema a carregar o balanço dos seus tokens. Pode vê-los em ", + "description": "Seguido de um link (aqui) para ver o balanço dos seus tokens" + }, + "twelveWords": { + "message": "Estas 12 palavras são a única forma de recuperar as suas contas na MetaMask.\nGuarde-as num local seguro e secreto." + }, + "typePassword": { + "message": "Digite a sua Palavra-passe" + }, + "uiWelcome": { + "message": "Bem-vindo ao seu Novo UI (Beta)" + }, + "uiWelcomeMessage": { + "message": "Está agora a usar o novo UI da MetaMask. Dê uma vista de olhos, experimenta as novas funcionalidades como enviar tokens e diga-nos se tiver algum problema." + }, + "unavailable": { + "message": "Indisponível" + }, + "unknown": { + "message": "Desconhecido" + }, + "unknownNetwork": { + "message": "Rede Privada Desconhecida" + }, + "unknownNetworkId": { + "message": "Identificador da rede desconhecido" + }, + "uriErrorMsg": { + "message": "Links requerem o prefixo HTTP/HTTPS apropriado." + }, + "usaOnly": { + "message": "Só nos EUA", + "description": "Usar esta taxa de câmbio está limitado a pessoas residentes nos EUA" + }, + "usedByClients": { + "message": "Utilizado por vários tipos de clientes" + }, + "useOldUI": { + "message": "Utilizar UI antigo" + }, + "validFileImport": { + "message": "Deve selecionar um ficheiro válido para importar." + }, + "vaultCreated": { + "message": "Cofre Criado" + }, + "viewAccount": { + "message": "Ver Conta" + }, + "visitWebSite": { + "message": "Visite o nosso site" + }, + "warning": { + "message": "Aviso" + }, + "welcomeBeta": { + "message": "Bem-vindo ao MetaMask Beta" + }, + "whatsThis": { + "message": "O que é isto?" + }, + "yourSigRequested": { + "message": "A sua assinatura está a ser pedida" + }, + "youSign": { + "message": "Está a assinar" + } +} diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 9c2ca0dc8..3e3909361 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -6,7 +6,6 @@ const EthQuery = require('ethjs-query') const TransactionStateManager = require('../lib/tx-state-manager') const TxGasUtil = require('../lib/tx-gas-utils') const PendingTransactionTracker = require('../lib/pending-tx-tracker') -const createId = require('../lib/random-id') const NonceTracker = require('../lib/nonce-tracker') /* @@ -92,8 +91,8 @@ module.exports = class TransactionController extends EventEmitter { this.pendingTxTracker.on('tx:warning', (txMeta) => { this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning') }) + this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId)) this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager)) - this.pendingTxTracker.on('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager)) this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => { if (!txMeta.firstRetryBlockNumber) { txMeta.firstRetryBlockNumber = latestBlockNumber @@ -186,14 +185,7 @@ module.exports = class TransactionController extends EventEmitter { // validate await this.txGasUtil.validateTxParams(txParams) // construct txMeta - const txMeta = { - id: createId(), - time: (new Date()).getTime(), - status: 'unapproved', - metamaskNetworkId: this.getNetwork(), - txParams: txParams, - loadingDefaults: true, - } + const txMeta = this.txStateManager.generateTxMeta({txParams}) this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) // add default tx params @@ -215,7 +207,6 @@ module.exports = class TransactionController extends EventEmitter { const txParams = txMeta.txParams // ensure value txMeta.gasPriceSpecified = Boolean(txParams.gasPrice) - txMeta.nonceSpecified = Boolean(txParams.nonce) let gasPrice = txParams.gasPrice if (!gasPrice) { gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice() @@ -226,11 +217,17 @@ module.exports = class TransactionController extends EventEmitter { return await this.txGasUtil.analyzeGasUsage(txMeta) } - async retryTransaction (txId) { - this.txStateManager.setTxStatusUnapproved(txId) - const txMeta = this.txStateManager.getTx(txId) - txMeta.lastGasPrice = txMeta.txParams.gasPrice - this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry') + async retryTransaction (originalTxId) { + const originalTxMeta = this.txStateManager.getTx(originalTxId) + const lastGasPrice = originalTxMeta.txParams.gasPrice + const txMeta = this.txStateManager.generateTxMeta({ + txParams: originalTxMeta.txParams, + lastGasPrice, + loadingDefaults: false, + }) + this.addTx(txMeta) + this.emit('newUnapprovedTx', txMeta) + return txMeta } async updateTransaction (txMeta) { @@ -253,11 +250,9 @@ module.exports = class TransactionController extends EventEmitter { // wait for a nonce nonceLock = await this.nonceTracker.getNonceLock(fromAddress) // add nonce to txParams - const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce - if (nonce > nonceLock.nextNonce) { - const message = `Specified nonce may not be larger than account's next valid nonce.` - throw new Error(message) - } + // if txMeta has lastGasPrice then it is a retry at same nonce with higher + // gas price transaction and their for the nonce should not be calculated + const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16)) // add nonce debugging information to txMeta txMeta.nonceDetails = nonceLock.nonceDetails @@ -314,6 +309,22 @@ module.exports = class TransactionController extends EventEmitter { // PRIVATE METHODS // + _markNonceDuplicatesDropped (txId) { + this.txStateManager.setTxStatusConfirmed(txId) + // get the confirmed transactions nonce and from address + const txMeta = this.txStateManager.getTx(txId) + const { nonce, from } = txMeta.txParams + const sameNonceTxs = this.txStateManager.getFilteredTxList({nonce, from}) + if (!sameNonceTxs.length) return + // mark all same nonce transactions as dropped and give i a replacedBy hash + sameNonceTxs.forEach((otherTxMeta) => { + if (otherTxMeta.id === txId) return + otherTxMeta.replacedBy = txMeta.hash + this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce') + this.txStateManager.setTxStatusDropped(otherTxMeta.id) + }) + } + _updateMemstore () { const unapprovedTxs = this.txStateManager.getUnapprovedTxList() const selectedAddressTxList = this.txStateManager.getFilteredTxList({ diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index 2eb006380..ad07c813f 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -1,9 +1,21 @@ const extend = require('xtend') const EventEmitter = require('events') const ObservableStore = require('obs-store') +const createId = require('./random-id') const ethUtil = require('ethereumjs-util') const txStateHistoryHelper = require('./tx-state-history-helper') +// STATUS METHODS + // statuses: + // - `'unapproved'` the user has not responded + // - `'rejected'` the user has responded no! + // - `'approved'` the user has approved the tx + // - `'signed'` the tx is signed + // - `'submitted'` the tx is sent to a server + // - `'confirmed'` the tx has been included in a block. + // - `'failed'` the tx failed for some reason, included on tx data. + // - `'dropped'` the tx nonce was already used + module.exports = class TransactionStateManager extends EventEmitter { constructor ({ initState, txHistoryLimit, getNetwork }) { super() @@ -16,6 +28,16 @@ module.exports = class TransactionStateManager extends EventEmitter { this.getNetwork = getNetwork } + generateTxMeta (opts) { + return extend({ + id: createId(), + time: (new Date()).getTime(), + status: 'unapproved', + metamaskNetworkId: this.getNetwork(), + loadingDefaults: true, + }, opts) + } + // Returns the number of txs for the current network. getTxCount () { return this.getTxList().length @@ -164,16 +186,6 @@ module.exports = class TransactionStateManager extends EventEmitter { }) } - // STATUS METHODS - // statuses: - // - `'unapproved'` the user has not responded - // - `'rejected'` the user has responded no! - // - `'approved'` the user has approved the tx - // - `'signed'` the tx is signed - // - `'submitted'` the tx is sent to a server - // - `'confirmed'` the tx has been included in a block. - // - `'failed'` the tx failed for some reason, included on tx data. - // get::set status // should return the status of the tx. @@ -202,7 +214,11 @@ module.exports = class TransactionStateManager extends EventEmitter { } // should update the status of the tx to 'submitted'. + // and add a time stamp for when it was called setTxStatusSubmitted (txId) { + const txMeta = this.getTx(txId) + txMeta.submittedTime = (new Date()).getTime() + this.updateTx(txMeta, 'txStateManager - add submitted time stamp') this._setTxStatus(txId, 'submitted') } @@ -211,6 +227,12 @@ module.exports = class TransactionStateManager extends EventEmitter { this._setTxStatus(txId, 'confirmed') } + // should update the status dropped + setTxStatusDropped (txId) { + this._setTxStatus(txId, 'dropped') + } + + setTxStatusFailed (txId, err) { const txMeta = this.getTx(txId) txMeta.err = { diff --git a/development/states/confirm-new-ui.json b/development/states/confirm-new-ui.json index 6ea8e64cd..6981781a9 100644 --- a/development/states/confirm-new-ui.json +++ b/development/states/confirm-new-ui.json @@ -116,7 +116,7 @@ "send": { "gasLimit": "0xea60", "gasPrice": "0xba43b7400", - "gasTotal": "0xb451dc41b578", + "gasTotal": "0xaa87bee538000", "tokenBalance": null, "from": { "address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", diff --git a/development/states/send-edit.json b/development/states/send-edit.json index 6ea8e64cd..6981781a9 100644 --- a/development/states/send-edit.json +++ b/development/states/send-edit.json @@ -116,7 +116,7 @@ "send": { "gasLimit": "0xea60", "gasPrice": "0xba43b7400", - "gasTotal": "0xb451dc41b578", + "gasTotal": "0xaa87bee538000", "tokenBalance": null, "from": { "address": "0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb", diff --git a/development/verify-locale-strings.js b/development/verify-locale-strings.js new file mode 100644 index 000000000..b8fe5a7dc --- /dev/null +++ b/development/verify-locale-strings.js @@ -0,0 +1,96 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Locale verification script +// +// usage: +// +// node app/scripts/verify-locale-strings.js <locale> +// +// will check the given locale against the strings in english +// +//////////////////////////////////////////////////////////////////////////////// + +var fs = require('fs') +var path = require('path') + +console.log('Locale Verification') + +var locale = process.argv[2] +if (!locale || locale == '') { + console.log('Must enter a locale as argument. exitting') + process.exit(1) +} + +console.log("verifying for locale " + locale) + +localeFilePath = path.join(process.cwd(), 'app', '_locales', locale, 'messages.json') +try { + localeObj = JSON.parse(fs.readFileSync(localeFilePath, 'utf8')); +} catch (e) { + if(e.code == 'ENOENT') { + console.log('Locale file not found') + } else { + console.log('Error opening your locale file: ', e) + } + process.exit(1) +} + +englishFilePath = path.join(process.cwd(), 'app', '_locales', 'en', 'messages.json') +try { + englishObj = JSON.parse(fs.readFileSync(englishFilePath, 'utf8')); +} catch (e) { + if(e.code == 'ENOENT') { + console.log("English File not found") + } else { + console.log("Error opening english locale file: ", e) + } + process.exit(1) +} + +console.log('\tverifying whether all your locale strings are contained in the english one') + +var counter = 0 +var foundErrorA = false +var notFound = []; +Object.keys(localeObj).forEach(function(key){ + if (!englishObj[key]) { + foundErrorA = true + notFound.push(key) + } + counter++ +}) + +if (foundErrorA) { + console.log('\nThe following string(s) is(are) not found in the english locale:') + notFound.forEach(function(key) { + console.log(key) + }) +} else { + console.log('\tall ' + counter +' strings declared in your locale were found in the english one') +} + +console.log('\n\tverifying whether your locale contains all english strings') + +var counter = 0 +var foundErrorB = false +var notFound = []; +Object.keys(englishObj).forEach(function(key){ + if (!localeObj[key]) { + foundErrorB = true + notFound.push(key) + } + counter++ +}) + +if (foundErrorB) { + console.log('\nThe following string(s) is(are) not found in the your locale:') + notFound.forEach(function(key) { + console.log(key) + }) +} else { + console.log('\tall ' + counter +' english strings were found in your locale!') +} + +if (!foundErrorA && !foundErrorB) { + console.log('You are good to go') +}
\ No newline at end of file diff --git a/docs/translating-guide.md b/docs/translating-guide.md index 62d444b5a..ae2dfecd3 100644 --- a/docs/translating-guide.md +++ b/docs/translating-guide.md @@ -14,5 +14,13 @@ That's it! When MetaMask is loaded on a computer with that language set as the s ## 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. +To automatically see if you are missing any phrases to translate, we have a script you can run (if you know how to use the command line). The script is: + +``` +node development/verify-locale-strings.js $YOUR_LOCALE +``` + +Where `$YOUR_LOCALE` is your [locale string](https://r12a.github.io/app-subtags/), i.e. the name of your language folder. + +To verify that your translation works in the app, you will need to [build a local copy](https://github.com/MetaMask/metamask-extension#building-locally) of MetaMask. You will need to change your browser language, your operating system language, and restart your browser (sorry it's so much work!). diff --git a/gulpfile.js b/gulpfile.js index adfb148a9..dbbb1e4ff 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -408,7 +408,11 @@ function bundleTask(opts) { .pipe(gulpif(debug, sourcemaps.init({ loadMaps: true }))) // Minification .pipe(gulpif(opts.isBuild, uglify({ - mangle: { reserved: [ 'MetamaskInpageProvider' ] }, + mangle: { reserved: [ 'MetamaskInpageProvider' ] }, + }))) + // Transpile to ES5 + .pipe(gulpif(opts.isBuild, babel({ + presets: ['env'] }))) // writes .map file .pipe(gulpif(debug, sourcemaps.write('./'))) diff --git a/old-ui/app/components/transaction-list-item.js b/old-ui/app/components/transaction-list-item.js index e7251df8d..7ab3414e5 100644 --- a/old-ui/app/components/transaction-list-item.js +++ b/old-ui/app/components/transaction-list-item.js @@ -29,9 +29,16 @@ function TransactionListItem () { } TransactionListItem.prototype.showRetryButton = function () { - const { transaction = {} } = this.props - const { status, time } = transaction - return status === 'submitted' && Date.now() - time > 30000 + const { transaction = {}, transactions } = this.props + const { status, submittedTime, txParams } = transaction + const currentNonce = txParams.nonce + const currentNonceTxs = transactions.filter(tx => tx.txParams.nonce === currentNonce) + const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') + const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[0] + const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce + && lastSubmittedTxWithCurrentNonce.id === transaction.id + + return currentTxIsLatestWithNonce && Date.now() - submittedTime > 30000 } TransactionListItem.prototype.render = function () { @@ -201,6 +208,11 @@ function formatDate (date) { function renderErrorOrWarning (transaction) { const { status, err, warning } = transaction + // show dropped + if (status === 'dropped') { + return h('span.dropped', ' (Dropped)') + } + // show rejected if (status === 'rejected') { return h('span.error', ' (Rejected)') diff --git a/old-ui/app/components/transaction-list.js b/old-ui/app/components/transaction-list.js index 345e3ca16..c77852921 100644 --- a/old-ui/app/components/transaction-list.js +++ b/old-ui/app/components/transaction-list.js @@ -62,7 +62,7 @@ TransactionList.prototype.render = function () { } return h(TransactionListItem, { transaction, i, network, key, - conversionRate, + conversionRate, transactions, showTx: (txId) => { this.props.viewPendingTx(txId) }, diff --git a/old-ui/app/css/index.css b/old-ui/app/css/index.css index 67c327f62..7af713336 100644 --- a/old-ui/app/css/index.css +++ b/old-ui/app/css/index.css @@ -247,6 +247,10 @@ app sections color: #FFAE00; } +.dropped { + color: #6195ED; +} + .lock { width: 50px; height: 50px; diff --git a/package.json b/package.json index 00587ece6..8f05bc7f1 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,10 @@ "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 && cross-env METAMASK_DEBUG=true node ./mascara/example/server", - "dist": "npm run dist:clear && npm install && gulp dist", + "dist": "npm run dist:clear && npm install && gulp dist && npm run test:es5", "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:es5": "es-check es5 ./dist/**/*.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", @@ -25,7 +26,7 @@ "test:flat:build:states": "node development/genStates.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": "mkdirp 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", "test:mascara:build:background": "browserify mascara/src/background.js -o dist/mascara/background.js", "test:mascara:build:tests": "browserify test/integration/lib/first-time.js -o dist/mascara/tests.js", @@ -200,6 +201,7 @@ "envify": "^4.0.0", "enzyme": "^3.3.0", "enzyme-adapter-react-15": "^1.0.5", + "es-check": "^2.0.2", "eslint-plugin-chai": "0.0.1", "eslint-plugin-mocha": "^4.9.0", "eslint-plugin-react": "^7.4.0", diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js index 594f5f0b0..573faaee3 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -93,7 +93,7 @@ async function runSendFlowTest(assert, done) { 'send gas field should show estimated gas total converted to USD' ) - const sendGasOpenCustomizeModalButton = await queryAsync($, '.send-v2__sliders-icon-container') + const sendGasOpenCustomizeModalButton = await queryAsync($, '.sliders-icon-container') sendGasOpenCustomizeModalButton[0].click() const customizeGasModal = await queryAsync($, '.send-v2__customize-gas') @@ -135,9 +135,9 @@ async function runSendFlowTest(assert, done) { assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name') 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] + const confirmScreenGas = confirmScreenRows.find('.currency-display__converted-value')[0] + assert.equal(confirmScreenGas.textContent, '3.60 USD', 'confirm screen should show correct gas') + const confirmScreenTotal = confirmScreenRows.find('.confirm-screen-row-info')[2] assert.equal(confirmScreenTotal.textContent, '2405.36 USD', 'confirm screen should show correct total') const confirmScreenBackButton = await queryAsync($, '.confirm-screen-back-button') diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index cc99afee4..712097fce 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -392,6 +392,49 @@ describe('Transaction Controller', function () { }) }) + describe('#retryTransaction', function () { + it('should create a new txMeta with the same txParams as the original one', function (done) { + let txParams = { + nonce: '0x00', + from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4', + to: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4', + data: '0x0', + } + txController.txStateManager._saveTxList([ + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams }, + ]) + txController.retryTransaction(1) + .then((txMeta) => { + assert.equal(txMeta.txParams.nonce, txParams.nonce, 'nonce should be the same') + assert.equal(txMeta.txParams.from, txParams.from, 'from should be the same') + assert.equal(txMeta.txParams.to, txParams.to, 'to should be the same') + assert.equal(txMeta.txParams.data, txParams.data, 'data should be the same') + assert.ok(('lastGasPrice' in txMeta), 'should have the key `lastGasPrice`') + assert.equal(txController.txStateManager.getTxList().length, 2) + done() + }).catch(done) + }) + }) + + describe('#_markNonceDuplicatesDropped', function () { + it('should mark all nonce duplicates as dropped without marking the confirmed transaction as dropped', function () { + txController.txStateManager._saveTxList([ + { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 4, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 6, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + { id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, history: [{}], txParams: { nonce: '0x01' } }, + ]) + txController._markNonceDuplicatesDropped(1) + const confirmedTx = txController.txStateManager.getTx(1) + const droppedTxs = txController.txStateManager.getFilteredTxList({ nonce: '0x01', status: 'dropped' }) + assert.equal(confirmedTx.status, 'confirmed', 'the confirmedTx should remain confirmed') + assert.equal(droppedTxs.length, 6, 'their should be 6 dropped txs') + + }) + }) describe('#getPendingTransactions', function () { beforeEach(function () { @@ -401,7 +444,7 @@ describe('Transaction Controller', function () { { id: 3, status: 'approved', metamaskNetworkId: currentNetworkId, txParams: {} }, { id: 4, status: 'signed', metamaskNetworkId: currentNetworkId, txParams: {} }, { id: 5, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 6, status: 'confimed', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 6, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }, { id: 7, status: 'failed', metamaskNetworkId: currentNetworkId, txParams: {} }, ]) }) diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js index 98924b6d9..00b1aab8e 100644 --- a/ui/app/accounts/import/index.js +++ b/ui/app/accounts/import/index.js @@ -36,7 +36,7 @@ AccountImportSubview.prototype.render = function () { h('div.new-account-import-form', [ h('.new-account-import-disclaimer', [ - h('span', 'Imported accounts will not be associated with your originally created MetaMask account seedphrase. Learn more about imported accounts '), + h('span', t('importAccountMsg')), h('span', { style: { cursor: 'pointer', @@ -47,12 +47,12 @@ AccountImportSubview.prototype.render = function () { url: 'https://metamask.helpscoutdocs.com/article/17-what-are-loose-accounts', }) }, - }, 'here'), + }, t('here')), ]), h('div.new-account-import-form__select-section', [ - h('div.new-account-import-form__select-label', 'Select Type'), + h('div.new-account-import-form__select-label', t('selectType')), h(Select, { className: 'new-account-import-form__select', diff --git a/ui/app/accounts/import/json.js b/ui/app/accounts/import/json.js index f638e07c5..326e052f2 100644 --- a/ui/app/accounts/import/json.js +++ b/ui/app/accounts/import/json.js @@ -85,7 +85,7 @@ class JsonImportSubview extends Component { const state = this.state if (!state) { - const message = 'You must select a valid file to import.' + const message = t('validFileImport') return this.props.displayWarning(message) } diff --git a/ui/app/actions.js b/ui/app/actions.js index 12cf10411..69c13ff88 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1286,8 +1286,10 @@ function retryTransaction (txId) { if (err) { return dispatch(actions.displayWarning(err.message)) } + const { selectedAddressTxList } = newState + const { id: newTxId } = selectedAddressTxList[selectedAddressTxList.length - 1] dispatch(actions.updateMetamaskState(newState)) - dispatch(actions.viewPendingTx(txId)) + dispatch(actions.viewPendingTx(newTxId)) }) } } diff --git a/ui/app/add-token.js b/ui/app/add-token.js index 917a7c9fb..9576a7aef 100644 --- a/ui/app/add-token.js +++ b/ui/app/add-token.js @@ -26,6 +26,7 @@ const fuse = new Fuse(contractList, { const actions = require('./actions') const ethUtil = require('ethereumjs-util') const { tokenInfoGetter } = require('./token-util') +const t = require('../i18n') const emptyAddr = '0x0000000000000000000000000000000000000000' @@ -139,28 +140,28 @@ AddTokenScreen.prototype.validate = function () { if (customAddress) { const validAddress = ethUtil.isValidAddress(customAddress) if (!validAddress) { - errors.customAddress = 'Address is invalid. ' + errors.customAddress = t('invalidAddress') } const validDecimals = customDecimals !== null && customDecimals >= 0 && customDecimals < 36 if (!validDecimals) { - errors.customDecimals = 'Decimals must be at least 0, and not over 36.' + errors.customDecimals = t('decimalsMustZerotoTen') } const symbolLen = customSymbol.trim().length const validSymbol = symbolLen > 0 && symbolLen < 10 if (!validSymbol) { - errors.customSymbol = 'Symbol must be between 0 and 10 characters.' + errors.customSymbol = t('symbolBetweenZeroTen') } const ownAddress = identitiesList.includes(standardAddress) if (ownAddress) { - errors.customAddress = 'Personal address detected. Input the token contract address.' + errors.customAddress = t('personalAddressDetected') } const tokenAlreadyAdded = this.checkExistingAddresses(customAddress) if (tokenAlreadyAdded) { - errors.customAddress = 'Token has already been added.' + errors.customAddress = t('tokenAlreadyAdded') } } else if ( Object.entries(selectedTokens) @@ -168,7 +169,7 @@ AddTokenScreen.prototype.validate = function () { isEmpty && !isSelected ), true) ) { - errors.tokenSelector = 'Must select at least 1 token.' + errors.tokenSelector = t('mustSelectOne') } return { @@ -198,7 +199,7 @@ AddTokenScreen.prototype.renderCustomForm = function () { 'add-token__add-custom-field--error': errors.customAddress, }), }, [ - h('div.add-token__add-custom-label', 'Token Address'), + h('div.add-token__add-custom-label', t('tokenAddress')), h('input.add-token__add-custom-input', { type: 'text', onChange: this.tokenAddressDidChange, @@ -211,7 +212,7 @@ AddTokenScreen.prototype.renderCustomForm = function () { 'add-token__add-custom-field--error': errors.customSymbol, }), }, [ - h('div.add-token__add-custom-label', 'Token Symbol'), + h('div.add-token__add-custom-label', t('tokenSymbol')), h('input.add-token__add-custom-input', { type: 'text', onChange: this.tokenSymbolDidChange, @@ -225,7 +226,7 @@ AddTokenScreen.prototype.renderCustomForm = function () { 'add-token__add-custom-field--error': errors.customDecimals, }), }, [ - h('div.add-token__add-custom-label', 'Decimals of Precision'), + h('div.add-token__add-custom-label', t('decimal')), h('input.add-token__add-custom-input', { type: 'number', onChange: this.tokenDecimalsDidChange, @@ -299,11 +300,11 @@ AddTokenScreen.prototype.renderConfirmation = function () { h('div.add-token', [ h('div.add-token__wrapper', [ h('div.add-token__title-container.add-token__confirmation-title', [ - h('div.add-token__title', 'Add Token'), - h('div.add-token__description', 'Would you like to add these tokens?'), + h('div.add-token__title', t('addToken')), + h('div.add-token__description', t('likeToAddTokens')), ]), h('div.add-token__content-container.add-token__confirmation-content', [ - h('div.add-token__description.add-token__confirmation-description', 'Your balances'), + h('div.add-token__description.add-token__confirmation-description', t('balances')), h('div.add-token__confirmation-token-list', Object.entries(tokens) .map(([ address, token ]) => ( @@ -322,10 +323,10 @@ AddTokenScreen.prototype.renderConfirmation = function () { h('div.add-token__buttons', [ h('button.btn-cancel.add-token__button', { onClick: () => this.setState({ isShowingConfirmation: false }), - }, 'Back'), + }, t('back')), h('button.btn-clear.add-token__button', { onClick: () => addTokens(tokens).then(goHome), - }, 'Add Tokens'), + }, t('addTokens')), ]), ]) ) @@ -341,15 +342,15 @@ AddTokenScreen.prototype.render = function () { h('div.add-token', [ h('div.add-token__wrapper', [ h('div.add-token__title-container', [ - h('div.add-token__title', 'Add Token'), - h('div.add-token__description', 'Keep track of the tokens you’ve bought with your MetaMask account. If you bought tokens using a different account, those tokens will not appear here.'), - h('div.add-token__description', 'Search for tokens or select from our list of popular tokens.'), + h('div.add-token__title', t('addToken')), + h('div.add-token__description', t('tokenWarning1')), + h('div.add-token__description', t('tokenSelection')), ]), h('div.add-token__content-container', [ h('div.add-token__input-container', [ h('input.add-token__input', { type: 'text', - placeholder: 'Search', + placeholder: t('search'), onChange: e => this.setState({ searchQuery: e.target.value }), }), h('div.add-token__search-input-error-message', errors.tokenSelector), @@ -363,7 +364,7 @@ AddTokenScreen.prototype.render = function () { h('div.add-token__add-custom', { onClick: () => this.setState({ isCollapsed: !isCollapsed }), }, [ - 'Add custom token', + t('addCustomToken'), h(`i.fa.fa-angle-${isCollapsed ? 'down' : 'up'}`), ]), this.renderCustomForm(), @@ -372,10 +373,10 @@ AddTokenScreen.prototype.render = function () { h('div.add-token__buttons', [ h('button.btn-cancel.add-token__button', { onClick: goHome, - }, 'Cancel'), + }, t('cancel')), h('button.btn-clear.add-token__button', { onClick: this.onNext, - }, 'Next'), + }, t('next')), ]), ]) ) diff --git a/ui/app/app.js b/ui/app/app.js index a1f402d85..f69efd1c7 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -132,7 +132,7 @@ App.prototype.render = function () { } = props const isLoadingNetwork = network === 'loading' && props.currentView.name !== 'config' const loadMessage = loadingMessage || isLoadingNetwork ? - `Connecting to ${this.getNetworkName()}` : null + this.getConnectingLabel() : null log.debug('Main ui render function') return ( @@ -550,6 +550,27 @@ App.prototype.toggleMetamaskActive = function () { } } +App.prototype.getConnectingLabel = function () { + const { provider } = this.props + const providerName = provider.type + + let name + + if (providerName === 'mainnet') { + name = t('connectingToMainnet') + } else if (providerName === 'ropsten') { + name = t('connectingToRopsten') + } else if (providerName === 'kovan') { + name = t('connectingToRopsten') + } else if (providerName === 'rinkeby') { + name = t('connectingToRinkeby') + } else { + name = t('connectingToUnknown') + } + + return name +} + App.prototype.getNetworkName = function () { const { provider } = this.props const providerName = provider.type @@ -557,15 +578,15 @@ App.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/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index ed0a3b45e..e44675880 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -22,12 +22,14 @@ const { conversionUtil, multiplyCurrencies, conversionGreaterThan, + conversionMax, subtractCurrencies, } = require('../../conversion-util') const { getGasPrice, getGasLimit, + getForceGasMin, conversionRateSelector, getSendAmount, getSelectedToken, @@ -45,6 +47,7 @@ function mapStateToProps (state) { return { gasPrice: getGasPrice(state), gasLimit: getGasLimit(state), + forceGasMin: getForceGasMin(state), conversionRate, amount: getSendAmount(state), maxModeOn: getSendMaxModeState(state), @@ -115,9 +118,9 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { updateSendAmount(maxAmount) } - updateGasPrice(gasPrice) - updateGasLimit(gasLimit) - updateGasTotal(gasTotal) + updateGasPrice(ethUtil.addHexPrefix(gasPrice)) + updateGasLimit(ethUtil.addHexPrefix(gasLimit)) + updateGasTotal(ethUtil.addHexPrefix(gasTotal)) hideModal() } @@ -218,7 +221,7 @@ CustomizeGasModal.prototype.convertAndSetGasPrice = function (newGasPrice) { } CustomizeGasModal.prototype.render = function () { - const { hideModal } = this.props + const { hideModal, forceGasMin } = this.props const { gasPrice, gasLimit, gasTotal, error, priceSigZeros, priceSigDec } = this.state let convertedGasPrice = conversionUtil(gasPrice, { @@ -230,6 +233,22 @@ CustomizeGasModal.prototype.render = function () { convertedGasPrice += convertedGasPrice.match(/[.]/) ? priceSigZeros : `${priceSigDec}${priceSigZeros}` + let newGasPrice = gasPrice + if (forceGasMin) { + const convertedMinPrice = conversionUtil(forceGasMin, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + }) + convertedGasPrice = conversionMax( + { value: convertedMinPrice, fromNumericBase: 'dec' }, + { value: convertedGasPrice, fromNumericBase: 'dec' } + ) + newGasPrice = conversionMax( + { value: gasPrice, fromNumericBase: 'hex' }, + { value: forceGasMin, fromNumericBase: 'hex' } + ) + } + const convertedGasLimit = conversionUtil(gasLimit, { fromNumericBase: 'hex', toNumericBase: 'dec', @@ -252,7 +271,7 @@ CustomizeGasModal.prototype.render = function () { h(GasModalCard, { value: convertedGasPrice, - min: MIN_GAS_PRICE_GWEI, + min: forceGasMin || MIN_GAS_PRICE_GWEI, // max: 1000, step: multiplyCurrencies(MIN_GAS_PRICE_GWEI, 10), onChange: value => this.convertAndSetGasPrice(value), @@ -288,7 +307,7 @@ CustomizeGasModal.prototype.render = function () { }, [t(this.props.localeMessages, 'cancel')]), h(`div.send-v2__customize-gas__save${error ? '__error' : ''}.allcaps`, { - onClick: () => !error && this.save(gasPrice, gasLimit, gasTotal), + onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal), }, [t(this.props.localeMessages, 'save')]), ]), diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index 3a72f575f..253b69b7a 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -8,7 +8,12 @@ const Identicon = require('../identicon') 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 { + conversionUtil, + addCurrencies, + multiplyCurrencies, +} = require('../../conversion-util') +const GasFeeDisplay = require('../send/gas-fee-display-v2') const t = require('../../../i18n-helper').getMessage const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') @@ -44,6 +49,7 @@ function mapDispatchToProps (dispatch) { to, value: amount, } = txParams + dispatch(actions.updateSend({ gasLimit, gasPrice, @@ -56,6 +62,29 @@ function mapDispatchToProps (dispatch) { dispatch(actions.showSendPage()) }, cancelTransaction: ({ id }) => dispatch(actions.cancelTx({ id })), + showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => { + const { id, txParams, lastGasPrice } = txMeta + const { gas: txGasLimit, gasPrice: txGasPrice } = txParams + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + fromDenomination: 'WEI', + })) + } + + dispatch(actions.updateSend({ + gasLimit: sendGasLimit || txGasLimit, + gasPrice: sendGasPrice || txGasPrice, + editingTransactionId: id, + gasTotal: sendGasTotal, + forceGasMin, + })) + dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) + }, } } @@ -140,6 +169,7 @@ ConfirmSendEther.prototype.getGasFee = function () { return { FIAT, ETH, + gasFeeInHex: txFeeBn.toString(16), } } @@ -147,7 +177,7 @@ ConfirmSendEther.prototype.getData = function () { const { identities } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} - const { FIAT: gasFeeInFIAT, ETH: gasFeeInETH } = this.getGasFee() + const { FIAT: gasFeeInFIAT, ETH: gasFeeInETH, gasFeeInHex } = this.getGasFee() const { FIAT: amountInFIAT, ETH: amountInETH } = this.getAmount() const totalInFIAT = addCurrencies(gasFeeInFIAT, amountInFIAT, { @@ -175,11 +205,20 @@ ConfirmSendEther.prototype.getData = function () { amountInETH, totalInFIAT, totalInETH, + gasFeeInHex, } } ConfirmSendEther.prototype.render = function () { - const { editTransaction, currentCurrency, clearSend } = this.props + const { + editTransaction, + currentCurrency, + clearSend, + conversionRate, + currentCurrency: convertedCurrency, + showCustomizeGasModal, + send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice }, + } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} @@ -193,13 +232,17 @@ ConfirmSendEther.prototype.render = function () { name: toName, }, memo, - gasFeeInFIAT, - gasFeeInETH, + gasFeeInHex, amountInFIAT, totalInFIAT, totalInETH, } = this.getData() + const title = txMeta.lastGasPrice ? 'Reprice Transaction' : 'Confirm' + const subtitle = txMeta.lastGasPrice + ? 'Increase your gas fee to attempt to overwrite and speed up your transaction' + : 'Please review your transaction.' + // This is from the latest master // It handles some of the errors that we are not currently handling // Leaving as comments fo reference @@ -218,11 +261,11 @@ ConfirmSendEther.prototype.render = function () { // Main Send token Card h('div.page-container', [ h('div.page-container__header', [ - h('button.confirm-screen-back-button', { + !txMeta.lastGasPrice && 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.`), + h('div.page-container__title', title), + h('div.page-container__subtitle', subtitle), ]), h('.page-container__content', [ h('div.flex-row.flex-center.confirm-screen-identicons', [ @@ -286,9 +329,12 @@ ConfirmSendEther.prototype.render = function () { h('section.flex-row.flex-center.confirm-screen-row', [ h('span.confirm-screen-label.confirm-screen-section-column', [ t(this.props.localeMessages, 'gasFee') ]), h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', `${gasFeeInFIAT} ${currentCurrency.toUpperCase()}`), - - h('div.confirm-screen-row-detail', `${gasFeeInETH} ETH`), + h(GasFeeDisplay, { + gasTotal: gasTotal || gasFeeInHex, + conversionRate, + convertedCurrency, + onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal), + }), ]), ]), @@ -449,6 +495,27 @@ ConfirmSendEther.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) + const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send + const { + lastGasPrice, + txParams: { + gasPrice: txGasPrice, + gas: txGasLimit, + }, + } = txData + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + })) + } + + txData.txParams.gasPrice = sendGasPrice || forceGasMin || txGasPrice + txData.txParams.gas = sendGasLimit || txGasLimit + // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 4cd6f020b..54963ae9a 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -9,6 +9,7 @@ const actions = require('../../actions') const t = require('../../../i18n-helper').getMessage const clone = require('clone') const Identicon = require('../identicon') +const GasFeeDisplay = require('../send/gas-fee-display-v2.js') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN const { @@ -89,6 +90,39 @@ function mapDispatchToProps (dispatch, ownProps) { })) dispatch(actions.showSendTokenPage()) }, + showCustomizeGasModal: (txMeta, sendGasLimit, sendGasPrice, sendGasTotal) => { + const { id, txParams, lastGasPrice } = txMeta + const { gas: txGasLimit, gasPrice: txGasPrice } = txParams + const tokenData = txParams.data && abiDecoder.decodeMethod(txParams.data) + const { params = [] } = tokenData + const { value: to } = params[0] || {} + const { value: tokenAmountInDec } = params[1] || {} + const tokenAmountInHex = conversionUtil(tokenAmountInDec, { + fromNumericBase: 'dec', + toNumericBase: 'hex', + }) + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + fromDenomination: 'WEI', + })) + } + + dispatch(actions.updateSend({ + gasLimit: sendGasLimit || txGasLimit, + gasPrice: sendGasPrice || txGasPrice, + editingTransactionId: id, + gasTotal: sendGasTotal, + to, + amount: tokenAmountInHex, + forceGasMin, + })) + dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) + }, } } @@ -188,6 +222,7 @@ ConfirmSendToken.prototype.getGasFee = function () { token: tokenExchangeRate ? tokenGas : null, + gasFeeInHex: gasTotal.toString(16), } } @@ -240,19 +275,25 @@ ConfirmSendToken.prototype.renderHeroAmount = function () { } ConfirmSendToken.prototype.renderGasFee = function () { - const { token: { symbol }, currentCurrency } = this.props - const { fiat: fiatGas, token: tokenGas, eth: ethGas } = this.getGasFee() + const { + currentCurrency: convertedCurrency, + conversionRate, + send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice }, + showCustomizeGasModal, + } = this.props + const txMeta = this.gatherTxMeta() + const { gasFeeInHex } = this.getGasFee() return ( h('section.flex-row.flex-center.confirm-screen-row', [ h('span.confirm-screen-label.confirm-screen-section-column', [ t(this.props.localeMessages, 'gasFee') ]), h('div.confirm-screen-section-column', [ - h('div.confirm-screen-row-info', `${fiatGas} ${currentCurrency}`), - - h( - 'div.confirm-screen-row-detail', - tokenGas ? `${tokenGas} ${symbol}` : `${ethGas} ETH` - ), + h(GasFeeDisplay, { + gasTotal: gasTotal || gasFeeInHex, + conversionRate, + convertedCurrency, + onClick: () => showCustomizeGasModal(txMeta, sendGasLimit, sendGasPrice, gasTotal), + }), ]), ]) ) @@ -308,16 +349,22 @@ ConfirmSendToken.prototype.render = function () { this.inputs = [] + const isTxReprice = Boolean(txMeta.lastGasPrice) + const title = isTxReprice ? t(this.props.localeMessages, 'reprice:title') : t(this.props.localeMessages, 'confirm') + const subtitle = isTxReprice + ? t(this.props.localeMessages, 'reprice:subtitle') + : t(this.props.localeMessages, 'pleaseReviewTransaction') + return ( h('div.confirm-screen-container.confirm-send-token', [ // Main Send token Card h('div.page-container', [ h('div.page-container__header', [ - h('button.confirm-screen-back-button', { + !txMeta.lastGasPrice && h('button.confirm-screen-back-button', { onClick: () => editTransaction(txMeta), }, t(this.props.localeMessages, 'edit')), - h('div.page-container__title', t(this.props.localeMessages, 'confirm')), - h('div.page-container__subtitle', t(this.props.localeMessages, 'pleaseReviewTransaction')), + h('div.page-container__title', title), + h('div.page-container__subtitle', subtitle), ]), h('.page-container__content', [ h('div.flex-row.flex-center.confirm-screen-identicons', [ @@ -379,7 +426,7 @@ ConfirmSendToken.prototype.render = function () { this.renderTotalPlusGas(), ]), - + ]), h('form#pending-tx-form', { @@ -442,6 +489,27 @@ ConfirmSendToken.prototype.gatherTxMeta = function () { const state = this.state const txData = clone(state.txData) || clone(props.txData) + const { gasPrice: sendGasPrice, gas: sendGasLimit } = props.send + const { + lastGasPrice, + txParams: { + gasPrice: txGasPrice, + gas: txGasLimit, + }, + } = txData + + let forceGasMin + if (lastGasPrice) { + forceGasMin = ethUtil.addHexPrefix(multiplyCurrencies(lastGasPrice, 1.1, { + multiplicandBase: 16, + multiplierBase: 10, + toNumericBase: 'hex', + })) + } + + txData.txParams.gasPrice = sendGasPrice || forceGasMin || txGasPrice + txData.txParams.gas = sendGasLimit || txGasLimit + // log.debug(`UI has defaulted to tx meta ${JSON.stringify(txData)}`) return txData } diff --git a/ui/app/components/send/gas-fee-display-v2.js b/ui/app/components/send/gas-fee-display-v2.js index c156e4482..04a43df2f 100644 --- a/ui/app/components/send/gas-fee-display-v2.js +++ b/ui/app/components/send/gas-fee-display-v2.js @@ -18,6 +18,7 @@ GasFeeDisplay.prototype.render = function () { onClick, primaryCurrency = 'ETH', convertedCurrency, + gasLoadingError, } = this.props return h('div.send-v2__gas-fee-display', [ @@ -31,13 +32,15 @@ GasFeeDisplay.prototype.render = function () { convertedPrefix: '$', readOnly: true, }) - : h('div.currency-display', t(this.props.localeMessages, 'loading')), + : gasLoadingError + ? h('div.currency-display.currency-display--message', t(this.props.localeMessages, 'setGasPrice')) + : h('div.currency-display', t(this.props.localeMessages, 'loading')), - h('button.send-v2__sliders-icon-container', { + h('button.sliders-icon-container', { onClick, - disabled: !gasTotal, + disabled: !gasTotal && !gasLoadingError, }, [ - h('i.fa.fa-sliders.send-v2__sliders-icon'), + h('i.fa.fa-sliders.sliders-icon'), ]), ]) diff --git a/ui/app/components/send/send-v2-container.js b/ui/app/components/send/send-v2-container.js index a2a3ed389..25902cfce 100644 --- a/ui/app/components/send/send-v2-container.js +++ b/ui/app/components/send/send-v2-container.js @@ -48,6 +48,7 @@ function mapStateToProps (state) { primaryCurrency, convertedCurrency: getCurrentCurrency(state), data, + selectedAddress, amountConversionRate: selectedToken ? tokenToFiatRate : conversionRate, tokenContract: getSelectedTokenContract(state), unapprovedTxs: state.metamask.unapprovedTxs, diff --git a/ui/app/components/tx-list-item.js b/ui/app/components/tx-list-item.js index db8621434..3819de195 100644 --- a/ui/app/components/tx-list-item.js +++ b/ui/app/components/tx-list-item.js @@ -9,19 +9,28 @@ abiDecoder.addABI(abi) const Identicon = require('./identicon') const contractMap = require('eth-contract-metadata') +const actions = require('../actions') const { conversionUtil, multiplyCurrencies } = require('../conversion-util') const { calcTokenAmount } = require('../token-util') const { getCurrentCurrency } = require('../selectors') const t = require('../../i18n-helper').getMessage -module.exports = connect(mapStateToProps)(TxListItem) +module.exports = connect(mapStateToProps, mapDispatchToProps)(TxListItem) function mapStateToProps (state) { return { tokens: state.metamask.tokens, currentCurrency: getCurrentCurrency(state), tokenExchangeRates: state.metamask.tokenExchangeRates, + selectedAddressTxList: state.metamask.selectedAddressTxList, + } +} + +function mapDispatchToProps (dispatch) { + return { + setSelectedToken: tokenAddress => dispatch(actions.setSelectedToken(tokenAddress)), + retryTransaction: transactionId => dispatch(actions.retryTransaction(transactionId)), } } @@ -32,6 +41,7 @@ function TxListItem () { this.state = { total: null, fiatTotal: null, + isTokenTx: null, } } @@ -40,12 +50,13 @@ TxListItem.prototype.componentDidMount = async function () { const decodedData = txParams.data && abiDecoder.decodeMethod(txParams.data) const { name: txDataName } = decodedData || {} + const isTokenTx = txDataName === 'transfer' - const { total, fiatTotal } = txDataName === 'transfer' + const { total, fiatTotal } = isTokenTx ? await this.getSendTokenTotal() : this.getSendEtherTotal() - this.setState({ total, fiatTotal }) + this.setState({ total, fiatTotal, isTokenTx }) } TxListItem.prototype.getAddressText = function () { @@ -168,22 +179,49 @@ TxListItem.prototype.getSendTokenTotal = async function () { } } +TxListItem.prototype.showRetryButton = function () { + const { + transactionSubmittedTime, + selectedAddressTxList, + transactionId, + txParams, + } = this.props + const currentNonce = txParams.nonce + const currentNonceTxs = selectedAddressTxList.filter(tx => tx.txParams.nonce === currentNonce) + const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') + const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[currentNonceSubmittedTxs.length - 1] + const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce + && lastSubmittedTxWithCurrentNonce.id === transactionId + + return currentTxIsLatestWithNonce && Date.now() - transactionSubmittedTime > 30000 +} + +TxListItem.prototype.setSelectedToken = function (tokenAddress) { + this.props.setSelectedToken(tokenAddress) +} + +TxListItem.prototype.resubmit = function () { + const { transactionId } = this.props + this.props.retryTransaction(transactionId) +} + TxListItem.prototype.render = function () { const { transactionStatus, transactionAmount, onClick, - transActionId, + transactionId, dateString, address, className, + txParams, } = this.props - const { total, fiatTotal } = this.state + const { total, fiatTotal, isTokenTx } = this.state const showFiatTotal = transactionAmount !== '0x0' && fiatTotal return h(`div${className || ''}`, { - key: transActionId, - onClick: () => onClick && onClick(transActionId), + key: transactionId, + onClick: () => onClick && onClick(transactionId), }, [ h(`div.flex-column.tx-list-item-wrapper`, {}, [ @@ -224,9 +262,10 @@ TxListItem.prototype.render = function () { className: classnames('tx-list-status', { 'tx-list-status--rejected': transactionStatus === 'rejected', 'tx-list-status--failed': transactionStatus === 'failed', + 'tx-list-status--dropped': transactionStatus === 'dropped', }), }, - transactionStatus, + this.txStatusIndicator(), ), ]), ]), @@ -241,6 +280,48 @@ TxListItem.prototype.render = function () { ]), ]), + + this.showRetryButton() && h('div.tx-list-item-retry-container', [ + + h('span.tx-list-item-retry-copy', 'Taking too long?'), + + h('span.tx-list-item-retry-link', { + onClick: (event) => { + event.stopPropagation() + if (isTokenTx) { + this.setSelectedToken(txParams.to) + } + this.resubmit() + }, + }, 'Increase the gas price on your transaction'), + + ]), + ]), // holding on icon from design ]) } + +TxListItem.prototype.txStatusIndicator = function () { + const { transactionStatus } = this.props + + let name + + if (transactionStatus === 'unapproved') { + name = t('unapproved') + } else if (transactionStatus === 'rejected') { + name = t('rejected') + } else if (transactionStatus === 'approved') { + name = t('approved') + } else if (transactionStatus === 'signed') { + name = t('signed') + } else if (transactionStatus === 'submitted') { + name = t('submitted') + } else if (transactionStatus === 'confirmed') { + name = t('confirmed') + } else if (transactionStatus === 'failed') { + name = t('failed') + } else if (transactionStatus === 'dropped') { + name = t('dropped') + } + return name +} diff --git a/ui/app/components/tx-list.js b/ui/app/components/tx-list.js index d536b2806..d8c882ae2 100644 --- a/ui/app/components/tx-list.js +++ b/ui/app/components/tx-list.js @@ -40,7 +40,7 @@ TxList.prototype.render = function () { return h('div.flex-column', [ h('div.flex-row.tx-list-header-wrapper', [ h('div.flex-row.tx-list-header', [ - h('div', 'transactions'), + h('div', t('transactions')), ]), ]), h('div.flex-column.tx-list-container', {}, [ @@ -75,9 +75,10 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa address: transaction.txParams.to, transactionStatus: transaction.status, transactionAmount: transaction.txParams.value, - transActionId: transaction.id, + transactionId: transaction.id, transactionHash: transaction.hash, transactionNetworkId: transaction.metamaskNetworkId, + transactionSubmittedTime: transaction.submittedTime, } const { @@ -85,29 +86,31 @@ TxList.prototype.renderTransactionListItem = function (transaction, conversionRa transactionStatus, transactionAmount, dateString, - transActionId, + transactionId, transactionHash, transactionNetworkId, + transactionSubmittedTime, } = props const { showConfTxPage } = this.props const opts = { - key: transActionId || transactionHash, + key: transactionId || transactionHash, txParams: transaction.txParams, transactionStatus, - transActionId, + transactionId, dateString, address, transactionAmount, transactionHash, conversionRate, tokenInfoGetter: this.tokenInfoGetter, + transactionSubmittedTime, } const isUnapproved = transactionStatus === 'unapproved' if (isUnapproved) { - opts.onClick = () => showConfTxPage({id: transActionId}) + opts.onClick = () => showConfTxPage({ id: transactionId }) opts.transactionStatus = t(this.props.localeMessages, 'Not Started') } else if (transactionHash) { opts.onClick = () => this.view(transactionHash, transactionNetworkId) diff --git a/ui/app/conf-tx.js b/ui/app/conf-tx.js index cbf5cd1d2..03848f490 100644 --- a/ui/app/conf-tx.js +++ b/ui/app/conf-tx.js @@ -40,6 +40,7 @@ function mapStateToProps (state) { currentCurrency: state.metamask.currentCurrency, blockGasLimit: state.metamask.currentBlockGasLimit, computedBalances: state.metamask.computedBalances, + selectedAddressTxList: state.metamask.selectedAddressTxList, } } @@ -48,6 +49,23 @@ function ConfirmTxScreen () { Component.call(this) } +ConfirmTxScreen.prototype.componentDidUpdate = function (prevProps) { + const { + unapprovedTxs, + network, + selectedAddressTxList, + } = this.props + const { index: prevIndex, unapprovedTxs: prevUnapprovedTxs } = prevProps + const prevUnconfTxList = txHelper(prevUnapprovedTxs, {}, {}, {}, network) + const prevTxData = prevUnconfTxList[prevIndex] || {} + const prevTx = selectedAddressTxList.find(({ id }) => id === prevTxData.id) || {} + const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network) + + if (prevTx.status === 'dropped' && unconfTxList.length === 0) { + this.goHome({}) + } +} + ConfirmTxScreen.prototype.render = function () { const props = this.props const { diff --git a/ui/app/conversion-util.js b/ui/app/conversion-util.js index ee42ebea1..d484ed16d 100644 --- a/ui/app/conversion-util.js +++ b/ui/app/conversion-util.js @@ -187,6 +187,18 @@ const conversionGreaterThan = ( return firstValue.gt(secondValue) } +const conversionMax = ( + { ...firstProps }, + { ...secondProps }, +) => { + const firstIsGreater = conversionGreaterThan( + { ...firstProps }, + { ...secondProps } + ) + + return firstIsGreater ? firstProps.value : secondProps.value +} + const conversionGTE = ( { ...firstProps }, { ...secondProps }, @@ -216,6 +228,7 @@ module.exports = { conversionGreaterThan, conversionGTE, conversionLTE, + conversionMax, toNegative, subtractCurrencies, } diff --git a/ui/app/css/itcss/components/account-menu.scss b/ui/app/css/itcss/components/account-menu.scss index 4752741aa..c4037d862 100644 --- a/ui/app/css/itcss/components/account-menu.scss +++ b/ui/app/css/itcss/components/account-menu.scss @@ -87,7 +87,6 @@ flex: 1 0 auto; display: flex; flex-flow: column nowrap; - padding-top: 4px; } &__check-mark { @@ -115,13 +114,11 @@ color: $white; font-size: 18px; font-weight: 300; - line-height: 16px; } &__balance { color: $dusty-gray; font-size: 14px; - line-height: 19px; } &__action { diff --git a/ui/app/css/itcss/components/confirm.scss b/ui/app/css/itcss/components/confirm.scss index 1977b49ae..abe138f54 100644 --- a/ui/app/css/itcss/components/confirm.scss +++ b/ui/app/css/itcss/components/confirm.scss @@ -242,6 +242,22 @@ section .confirm-screen-account-number, } } +@media screen and (max-width: 379px) { + .confirm-screen-row { + span.confirm-screen-section-column { + flex: 0.4; + } + + div.confirm-screen-section-column { + flex: 0.6; + } + + .currency-display__input { + font-size: 14px; + } + } +} + .confirm-screen-row-detail { font-size: 12px; line-height: 16px; diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss index c32d1de5e..374cb71b6 100644 --- a/ui/app/css/itcss/components/network.scss +++ b/ui/app/css/itcss/components/network.scss @@ -10,8 +10,9 @@ .network-component.pointer { border: 2px solid $silver; border-radius: 82px; - padding: 3px; + padding: 7px 3px; flex: 0 0 auto; + display: flex; &.ethereum-network .menu-icon-circle div { background-color: rgba(3, 135, 137, .7) !important; diff --git a/ui/app/css/itcss/components/newui-sections.scss b/ui/app/css/itcss/components/newui-sections.scss index 5cdda5e6c..777a82318 100644 --- a/ui/app/css/itcss/components/newui-sections.scss +++ b/ui/app/css/itcss/components/newui-sections.scss @@ -265,7 +265,6 @@ $wallet-view-bg: $alabaster; .account-name { font-size: 24px; font-weight: 300; - line-height: 20px; color: $black; margin-top: 8px; margin-bottom: .9rem; diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss index bb17e53cd..bdea1b008 100644 --- a/ui/app/css/itcss/components/send.scss +++ b/ui/app/css/itcss/components/send.scss @@ -660,6 +660,13 @@ &__gas-fee-display { width: 100%; + position: relative; + + .currency-display--message { + padding: 8px 38px 8px 10px; + display: flex; + align-items: center; + } } &__sliders-icon-container { @@ -885,3 +892,23 @@ } } } + +.sliders-icon-container { + display: flex; + align-items: center; + justify-content: center; + height: 24px; + width: 24px; + border: 1px solid $curious-blue; + border-radius: 4px; + background-color: $white; + position: absolute; + right: 15px; + top: 14px; + cursor: pointer; + font-size: 1em; +} + +.sliders-icon { + color: $curious-blue; +}
\ No newline at end of file diff --git a/ui/app/css/itcss/components/transaction-list.scss b/ui/app/css/itcss/components/transaction-list.scss index c3df493df..d03faf486 100644 --- a/ui/app/css/itcss/components/transaction-list.scss +++ b/ui/app/css/itcss/components/transaction-list.scss @@ -97,7 +97,7 @@ .tx-list-content-wrapper { align-items: stretch; - margin-bottom: 4px; + margin: 4px 0; flex: 1 0 auto; width: 100%; display: flex; @@ -126,6 +126,54 @@ } } +.tx-list-item-retry-container { + background: #d1edff; + width: 100%; + border-radius: 4px; + font-size: 0.8em; + display: flex; + justify-content: center; + margin-left: 44px; + width: calc(100% - 44px); + + @media screen and (min-width: 576px) and (max-width: 679px) { + flex-flow: column; + align-items: center; + } + + @media screen and (min-width: 380px) and (max-width: 575px) { + flex-flow: row; + } + + @media screen and (max-width: 379px) { + flex-flow: column; + align-items: center; + } +} + +.tx-list-item-retry-copy { + font-family: Roboto; +} + +.tx-list-item-retry-link { + text-decoration: underline; + margin-left: 6px; + cursor: pointer; + + @media screen and (min-width: 576px) and (max-width: 679px) { + margin-left: 0px; + } + + @media screen and (min-width: 380px) and (max-width: 575px) { + margin-left: 6px; + } + + @media screen and (max-width: 379px) { + margin-left: 0px; + text-align: center; + } +} + .tx-list-date { color: $dusty-gray; font-size: 12px; @@ -136,6 +184,7 @@ align-self: center; flex: 0 0 auto; margin-right: 16px; + display: flex; } .tx-list-account-and-status-wrapper { @@ -189,6 +238,10 @@ .tx-list-status--failed { color: $monzo; } + + .tx-list-status--dropped { + opacity: 0.5; + } } .tx-list-item { diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss index 1fbd9896f..1e226b93e 100644 --- a/ui/app/css/itcss/generic/index.scss +++ b/ui/app/css/itcss/generic/index.scss @@ -13,7 +13,6 @@ body { font-family: Roboto, Arial; color: #4d4d4d; font-weight: 300; - line-height: 1.4em; background: #f7f7f7; width: 100%; height: 100%; @@ -103,6 +102,7 @@ input.large-input { &::after { content: '\00D7'; font-size: 40px; + line-height: 20px; } } diff --git a/ui/app/css/itcss/generic/reset.scss b/ui/app/css/itcss/generic/reset.scss index e054d533e..a417a0453 100644 --- a/ui/app/css/itcss/generic/reset.scss +++ b/ui/app/css/itcss/generic/reset.scss @@ -112,10 +112,6 @@ section { display: block; } -body { - line-height: 1; -} - ol, ul { list-style: none; diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss index d96c1ae43..640fd95b8 100644 --- a/ui/app/css/itcss/settings/variables.scss +++ b/ui/app/css/itcss/settings/variables.scss @@ -46,6 +46,7 @@ $manatee: #93949d; $spindle: #c7ddec; $mid-gray: #5b5d67; $cape-cod: #38393a; +$onahau: #d1edff; $java: #29b6af; $wild-strawberry: #ff4a8d; $cornflower-blue: #7057ff; diff --git a/ui/app/keychains/hd/recover-seed/confirmation.js b/ui/app/keychains/hd/recover-seed/confirmation.js index 5f323f7a1..4a62c8803 100644 --- a/ui/app/keychains/hd/recover-seed/confirmation.js +++ b/ui/app/keychains/hd/recover-seed/confirmation.js @@ -4,6 +4,7 @@ const Component = require('react').Component const connect = require('../../../metamask-connect') const h = require('react-hyperscript') const actions = require('../../../actions') +const t = require('../../../../i18n') module.exports = connect(mapStateToProps)(RevealSeedConfirmation) @@ -49,13 +50,13 @@ RevealSeedConfirmation.prototype.render = function () { }, }, [ - h('h4', 'Do not recover your seed words in a public place! These words can be used to steal all your accounts.'), + h('h4', t('revealSeedWordsWarning')), // confirmation h('input.large-input.letter-spacey', { type: 'password', id: 'password-box', - placeholder: 'Enter your password to confirm', + placeholder: t('enterPasswordConfirm'), onKeyPress: this.checkConfirmation.bind(this), style: { width: 260, @@ -91,7 +92,7 @@ RevealSeedConfirmation.prototype.render = function () { ), props.inProgress && ( - h('span.in-progress-notification', 'Generating Seed...') + h('span.in-progress-notification', t('generatingSeed')) ), ]), ]) diff --git a/ui/app/keychains/hd/restore-vault.js b/ui/app/keychains/hd/restore-vault.js index 0a604f505..164cf28dc 100644 --- a/ui/app/keychains/hd/restore-vault.js +++ b/ui/app/keychains/hd/restore-vault.js @@ -2,6 +2,7 @@ const inherits = require('util').inherits const PersistentForm = require('../../../lib/persistent-form') const connect = require('../../metamask-connect') const h = require('react-hyperscript') +const t = require('../../../i18n') const actions = require('../../actions') module.exports = connect(mapStateToProps)(RestoreVaultScreen) @@ -36,23 +37,23 @@ RestoreVaultScreen.prototype.render = function () { padding: 6, }, }, [ - 'Restore Vault', + t('restoreVault'), ]), // wallet seed entry - h('h3', 'Wallet Seed'), + h('h3', t('walletSeed')), h('textarea.twelve-word-phrase.letter-spacey', { dataset: { persistentFormId: 'wallet-seed', }, - placeholder: 'Enter your secret twelve word phrase here to restore your vault.', + placeholder: t('secretPhrase'), }), // password h('input.large-input.letter-spacey', { type: 'password', id: 'password-box', - placeholder: 'New Password (min 8 chars)', + placeholder: t('newPassword8Chars'), dataset: { persistentFormId: 'password', }, @@ -66,7 +67,7 @@ RestoreVaultScreen.prototype.render = function () { h('input.large-input.letter-spacey', { type: 'password', id: 'password-box-confirm', - placeholder: 'Confirm Password', + placeholder: t('confirmPassword'), onKeyPress: this.createOnEnter.bind(this), dataset: { persistentFormId: 'password-confirmation', @@ -93,16 +94,20 @@ RestoreVaultScreen.prototype.render = function () { // cancel h('button.primary', { onClick: this.showInitializeMenu.bind(this), - }, 'CANCEL'), + style: { + textTransform: 'uppercase', + }, + }, t('cancel')), // submit h('button.primary', { onClick: this.createNewVaultAndRestore.bind(this), - }, 'OK'), - + style: { + textTransform: 'uppercase', + }, + }, t('ok')), ]), ]) - ) } @@ -131,13 +136,13 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () { var passwordConfirmBox = document.getElementById('password-box-confirm') var passwordConfirm = passwordConfirmBox.value if (password.length < 8) { - this.warning = 'Password not long enough' + this.warning = t('passwordNotLongEnough') this.props.dispatch(actions.displayWarning(this.warning)) return } if (password !== passwordConfirm) { - this.warning = 'Passwords don\'t match' + this.warning = t('passwordsDontMatch') this.props.dispatch(actions.displayWarning(this.warning)) return } @@ -147,18 +152,18 @@ RestoreVaultScreen.prototype.createNewVaultAndRestore = function () { // 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.warning = t('spaceBetween') 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)) + this.warning = t('loweCaseWords') + this.props.dispatch(actions.displayWarning(this.warning)) return } if (seed.split(' ').length !== 12) { - this.warning = 'seed phrases are 12 words long' + this.warning = t('seedPhraseReq') this.props.dispatch(actions.displayWarning(this.warning)) return } diff --git a/ui/app/reducers/metamask.js b/ui/app/reducers/metamask.js index 611c55391..6e226b0e6 100644 --- a/ui/app/reducers/metamask.js +++ b/ui/app/reducers/metamask.js @@ -38,6 +38,7 @@ function reduceMetamask (state, action) { errors: {}, maxModeOn: false, editingTransactionId: null, + forceGasMin: null, }, coinOptions: {}, useBlockie: false, @@ -298,6 +299,7 @@ function reduceMetamask (state, action) { memo: '', errors: {}, editingTransactionId: null, + forceGasMin: null, }, }) diff --git a/ui/app/selectors.js b/ui/app/selectors.js index 5d2635775..d37c26f7e 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -18,6 +18,7 @@ const selectors = { getCurrentAccountWithSendEtherInfo, getGasPrice, getGasLimit, + getForceGasMin, getAddressBook, getSendFrom, getCurrentCurrency, @@ -130,6 +131,10 @@ function getGasLimit (state) { return state.metamask.send.gasLimit } +function getForceGasMin (state) { + return state.metamask.send.forceGasMin +} + function getSendFrom (state) { return state.metamask.send.from } diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index fc1df1f51..31118378d 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -1,6 +1,7 @@ const { inherits } = require('util') const PersistentForm = require('../lib/persistent-form') const h = require('react-hyperscript') +const t = require('../i18n') const ethAbi = require('ethereumjs-abi') const ethUtil = require('ethereumjs-util') @@ -42,6 +43,7 @@ function SendTransactionScreen () { to: null, amount: null, }, + gasLoadingError: false, } this.handleToChange = this.handleToChange.bind(this) @@ -128,6 +130,10 @@ SendTransactionScreen.prototype.updateGas = function () { .then(([gasPrice, gas]) => { const newGasTotal = this.getGasTotal(gas, gasPrice) updateGasTotal(newGasTotal) + this.setState({ gasLoadingError: false }) + }) + .catch(err => { + this.setState({ gasLoadingError: true }) }) } else { const newGasTotal = this.getGasTotal(gasLimit, gasPrice) @@ -180,13 +186,12 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { SendTransactionScreen.prototype.renderHeader = function () { const { selectedToken, clearSend, goHome } = this.props - const tokenText = selectedToken ? 'tokens' : 'ETH' return h('div.page-container__header', [ - h('div.page-container__title', selectedToken ? 'Send Tokens' : 'Send ETH'), + h('div.page-container__title', selectedToken ? t('sendTokens') : t('sendETH')), - h('div.page-container__subtitle', `Only send ${tokenText} to an Ethereum address.`), + h('div.page-container__subtitle', t('onlySendToEtherAddress')), h('div.page-container__header-close', { onClick: () => { @@ -257,11 +262,11 @@ SendTransactionScreen.prototype.handleToChange = function (to) { let toError = null if (!to) { - toError = 'Required' + toError = t('required') } else if (!isValidAddress(to)) { - toError = 'Recipient address is invalid' + toError = t('invalidAddressRecipient') } else if (to === from) { - toError = 'From and To address cannot be the same' + toError = t('fromToSame') } updateSendTo(to) @@ -277,9 +282,9 @@ SendTransactionScreen.prototype.renderToRow = function () { h('div.send-v2__form-label', [ - 'To:', + t('to'), - this.renderErrorMessage('to'), + this.renderErrorMessage(t('to')), ]), @@ -377,11 +382,11 @@ SendTransactionScreen.prototype.validateAmount = function (value) { ) if (conversionRate && !sufficientBalance) { - amountError = 'Insufficient funds.' + amountError = t('insufficientFunds') } else if (verifyTokenBalance && !sufficientTokens) { - amountError = 'Insufficient tokens.' + amountError = t('insufficientTokens') } else if (amountLessThanZero) { - amountError = 'Can not send negative amounts of ETH.' + amountError = t('negativeETH') } updateSendErrors({ amount: amountError }) @@ -411,7 +416,7 @@ SendTransactionScreen.prototype.renderAmountRow = function () { setMaxModeTo(true) this.setAmountToMax() }, - }, [ !maxModeOn ? 'Max' : '' ]), + }, [ !maxModeOn ? t('max') : '' ]), ]), h('div.send-v2__form-field', [ @@ -436,10 +441,11 @@ SendTransactionScreen.prototype.renderGasRow = function () { showCustomizeGasModal, gasTotal, } = this.props + const { gasLoadingError } = this.state return h('div.send-v2__form-row', [ - h('div.send-v2__form-label', 'Gas fee:'), + h('div.send-v2__form-label', h('gasFee')), h('div.send-v2__form-field', [ @@ -448,6 +454,7 @@ SendTransactionScreen.prototype.renderGasRow = function () { conversionRate, convertedCurrency, onClick: showCustomizeGasModal, + gasLoadingError, }), ]), @@ -507,11 +514,11 @@ SendTransactionScreen.prototype.renderFooter = function () { clearSend() goHome() }, - }, 'Cancel'), + }, t('cancel')), h('button.btn-clear.page-container__footer-button', { disabled: !noErrors || !gasTotal || missingTokenBalance, onClick: event => this.onSubmit(event), - }, 'Next'), + }, t('next')), ]) } @@ -571,9 +578,11 @@ SendTransactionScreen.prototype.getEditedTx = function () { data, }) } else { + const data = unapprovedTxs[editingTransactionId].txParams.data Object.assign(editingTx.txParams, { value: ethUtil.addHexPrefix(amount), to: ethUtil.addHexPrefix(to), + data, }) } diff --git a/ui/app/settings.js b/ui/app/settings.js index 1412606e5..f75b1e8b4 100644 --- a/ui/app/settings.js +++ b/ui/app/settings.js @@ -10,6 +10,7 @@ const TabBar = require('./components/tab-bar') const SimpleDropdown = require('./components/dropdowns/simple-dropdown') const ToggleButton = require('react-toggle-button') const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/config').enums +const t = require('../i18n') const getInfuraCurrencyOptions = () => { const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { @@ -61,8 +62,8 @@ class Settings extends Component { return h('div.settings__tabs', [ h(TabBar, { tabs: [ - { content: 'Settings', key: 'settings' }, - { content: 'Info', key: 'info' }, + { content: t('settings'), key: 'settings' }, + { content: t('info'), key: 'info' }, ], defaultTab: activeTab, tabSelected: key => this.setState({ activeTab: key }), @@ -75,7 +76,7 @@ class Settings extends Component { return h('div.settings__content-row', [ h('div.settings__content-item', [ - h('span', 'Use Blockies Identicon'), + h('span', t('blockiesIdenticon')), ]), h('div.settings__content-item', [ h('div.settings__content-item-col', [ @@ -95,13 +96,13 @@ class Settings extends Component { return h('div.settings__content-row', [ h('div.settings__content-item', [ - h('span', 'Current Conversion'), + h('span', t('currentConversion')), h('span.settings__content-description', `Updated ${Date(conversionDate)}`), ]), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h(SimpleDropdown, { - placeholder: 'Select Currency', + placeholder: t('selectCurrency'), options: getInfuraCurrencyOptions(), selectedOption: currentCurrency, onSelect: newCurrency => setCurrentCurrency(newCurrency), @@ -141,31 +142,31 @@ class Settings extends Component { switch (provider.type) { case 'mainnet': - title = 'Current Network' - value = 'Main Ethereum Network' + title = t('currentNetwork') + value = t('mainnet') color = '#038789' break case 'ropsten': - title = 'Current Network' - value = 'Ropsten Test Network' + title = t('currentNetwork') + value = t('ropsten') color = '#e91550' break case 'kovan': - title = 'Current Network' - value = 'Kovan Test Network' + title = t('currentNetwork') + value = t('kovan') color = '#690496' break case 'rinkeby': - title = 'Current Network' - value = 'Rinkeby Test Network' + title = t('currentNetwork') + value = t('rinkeby') color = '#ebb33f' break default: - title = 'Current RPC' + title = t('currentRpc') value = provider.rpcTarget } @@ -186,12 +187,12 @@ class Settings extends Component { return ( h('div.settings__content-row', [ h('div.settings__content-item', [ - h('span', 'New RPC URL'), + h('span', t('newRPC')), ]), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h('input.settings__input', { - placeholder: 'New RPC URL', + placeholder: t('newRPC'), onChange: event => this.setState({ newRpc: event.target.value }), onKeyPress: event => { if (event.key === 'Enter') { @@ -204,7 +205,7 @@ class Settings extends Component { event.preventDefault() this.validateRpc(this.state.newRpc) }, - }, 'Save'), + }, t('save')), ]), ]), ]) @@ -220,9 +221,9 @@ class Settings extends Component { const appendedRpc = `http://${newRpc}` if (validUrl.isWebUri(appendedRpc)) { - displayWarning('URIs require the appropriate HTTP/HTTPS prefix.') + displayWarning(t('uriErrorMsg')) } else { - displayWarning('Invalid RPC URI') + displayWarning(t('invalidRPC')) } } } @@ -231,10 +232,10 @@ class Settings extends Component { return ( h('div.settings__content-row', [ h('div.settings__content-item', [ - h('div', 'State Logs'), + h('div', t('stateLogs')), h( 'div.settings__content-description', - 'State logs contain your public account addresses and sent transactions.' + t('stateLogsDescription') ), ]), h('div.settings__content-item', [ @@ -243,13 +244,13 @@ class Settings extends Component { onClick (event) { window.logStateString((err, result) => { if (err) { - this.state.dispatch(actions.displayWarning('Error in retrieving state logs.')) + this.state.dispatch(actions.displayWarning(t('stateLogError'))) } else { exportAsFile('MetaMask State Logs.json', result) } }) }, - }, 'Download State Logs'), + }, t('downloadStateLogs')), ]), ]), ]) @@ -261,7 +262,7 @@ class Settings extends Component { return ( h('div.settings__content-row', [ - h('div.settings__content-item', 'Reveal Seed Words'), + h('div.settings__content-item', t('revealSeedWords')), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h('button.settings__clear-button.settings__clear-button--red', { @@ -269,7 +270,7 @@ class Settings extends Component { event.preventDefault() revealSeedConfirmation() }, - }, 'Reveal Seed Words'), + }, t('revealSeedWords')), ]), ]), ]) @@ -281,7 +282,7 @@ class Settings extends Component { return ( h('div.settings__content-row', [ - h('div.settings__content-item', 'Use old UI'), + h('div.settings__content-item', t('useOldUI')), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h('button.settings__clear-button.settings__clear-button--orange', { @@ -289,7 +290,7 @@ class Settings extends Component { event.preventDefault() setFeatureFlagToBeta() }, - }, 'Use old UI'), + }, t('useOldUI')), ]), ]), ]) @@ -300,7 +301,7 @@ class Settings extends Component { const { showResetAccountConfirmationModal } = this.props return h('div.settings__content-row', [ - h('div.settings__content-item', 'Reset Account'), + h('div.settings__content-item', t('resetAccount')), h('div.settings__content-item', [ h('div.settings__content-item-col', [ h('button.settings__clear-button.settings__clear-button--orange', { @@ -308,7 +309,7 @@ class Settings extends Component { event.preventDefault() showResetAccountConfirmationModal() }, - }, 'Reset Account'), + }, t('resetAccount')), ]), ]), ]) @@ -344,13 +345,13 @@ class Settings extends Component { renderInfoLinks () { return ( h('div.settings__content-item.settings__content-item--without-height', [ - h('div.settings__info-link-header', 'Links'), + h('div.settings__info-link-header', t('links')), h('div.settings__info-link-item', [ h('a', { href: 'https://metamask.io/privacy.html', target: '_blank', }, [ - h('span.settings__info-link', 'Privacy Policy'), + h('span.settings__info-link', t('privacyMsg')), ]), ]), h('div.settings__info-link-item', [ @@ -358,7 +359,7 @@ class Settings extends Component { href: 'https://metamask.io/terms.html', target: '_blank', }, [ - h('span.settings__info-link', 'Terms of Use'), + h('span.settings__info-link', t('terms')), ]), ]), h('div.settings__info-link-item', [ @@ -366,7 +367,7 @@ class Settings extends Component { href: 'https://metamask.io/attributions.html', target: '_blank', }, [ - h('span.settings__info-link', 'Attributions'), + h('span.settings__info-link', t('attributions')), ]), ]), h('hr.settings__info-separator'), @@ -375,7 +376,7 @@ class Settings extends Component { href: 'https://support.metamask.io', target: '_blank', }, [ - h('span.settings__info-link', 'Visit our Support Center'), + h('span.settings__info-link', t('supportCenter')), ]), ]), h('div.settings__info-link-item', [ @@ -383,7 +384,7 @@ class Settings extends Component { href: 'https://metamask.io/', target: '_blank', }, [ - h('span.settings__info-link', 'Visit our web site'), + h('span.settings__info-link', t('visitWebSite')), ]), ]), h('div.settings__info-link-item', [ @@ -391,7 +392,7 @@ class Settings extends Component { target: '_blank', href: 'mailto:help@metamask.io?subject=Feedback', }, [ - h('span.settings__info-link', 'Email us!'), + h('span.settings__info-link', t('emailUs')), ]), ]), ]) @@ -413,7 +414,7 @@ class Settings extends Component { h('div.settings__info-item', [ h( 'div.settings__info-about', - 'MetaMask is designed and built in California.' + t('builtInCalifornia') ), ]), ]), @@ -490,3 +491,4 @@ const mapDispatchToProps = dispatch => { } module.exports = connect(mapStateToProps, mapDispatchToProps)(Settings) + diff --git a/ui/app/unlock.js b/ui/app/unlock.js index 8b58ba1a8..423e76e10 100644 --- a/ui/app/unlock.js +++ b/ui/app/unlock.js @@ -67,7 +67,7 @@ UnlockScreen.prototype.render = function () { style: { margin: 10, }, - }, 'Log In'), + }, t('login')), h('p.pointer', { onClick: () => { @@ -81,7 +81,7 @@ UnlockScreen.prototype.render = function () { color: 'rgb(247, 134, 28)', textDecoration: 'underline', }, - }, 'Restore from seed phrase'), + }, t('restoreFromSeed')), h('p.pointer', { onClick: () => { @@ -94,7 +94,7 @@ UnlockScreen.prototype.render = function () { textDecoration: 'underline', marginTop: '32px', }, - }, 'Use classic interface'), + }, t('classicInterface')), ]) ) } |