Browse Source

Merge remote-tracking branch 'upstream/develop' into feat/firmware-update-errors

master
Gaëtan Renaudeau 7 years ago
parent
commit
80cb8ffdcc
  1. 6
      src/components/FeesField/GenericContainer.js
  2. 2
      src/components/Onboarding/steps/Analytics.js
  3. 6
      src/components/RecipientAddress/index.js
  4. 2
      src/components/SettingsPage/ReleaseNotesButton.js
  5. 4
      src/components/SettingsPage/SettingsSection.js
  6. 6
      src/components/SettingsPage/index.js
  7. 45
      src/components/SettingsPage/sections/About.js
  8. 72
      src/components/SettingsPage/sections/Help.js
  9. 2
      src/components/base/LabelWithExternalIcon.js
  10. 15
      src/components/modals/AddAccounts/steps/03-step-import.js
  11. 2
      src/components/modals/Send/fields/RecipientField.js
  12. 2
      src/components/modals/Send/steps/01-step-amount.js
  13. 2
      src/components/modals/Send/steps/02-step-connect-device.js
  14. 2
      src/components/modals/Send/steps/03-step-verification.js
  15. 4
      src/components/modals/Send/steps/04-step-confirmation.js
  16. 2
      src/config/constants.js
  17. 3
      src/helpers/accountName.js
  18. 17
      src/helpers/bip32.js
  19. 86
      src/helpers/libcore.js
  20. 2
      src/icons/Currencies.js
  21. 2
      src/icons/Display.js
  22. 2
      src/icons/Help.js
  23. 7
      static/i18n/en/app.yml
  24. 35
      static/i18n/fr/app.yml
  25. 8
      static/i18n/fr/errors.yml
  26. 26
      static/i18n/fr/onboarding.yml

6
src/components/FeesField/GenericContainer.js

@ -7,11 +7,15 @@ import LabelWithExternalIcon from 'components/base/LabelWithExternalIcon'
import { translate } from 'react-i18next'
import { openURL } from 'helpers/linking'
import { urls } from 'config/support'
import { track } from 'analytics/segment'
export default translate()(({ children, t }: { children: React$Node, t: * }) => (
<Box flow={1}>
<LabelWithExternalIcon
onClick={() => openURL(urls.feesMoreInfo)}
onClick={() => {
openURL(urls.feesMoreInfo)
track('Send Flow Fees Help Requested')
}}
label={t('app:send.steps.amount.fees')}
/>
<Box horizontal flow={5}>

2
src/components/Onboarding/steps/Analytics.js

@ -6,13 +6,13 @@ import { connect } from 'react-redux'
import { saveSettings } from 'actions/settings'
import Box from 'components/base/Box'
import Switch from 'components/base/Switch'
import FakeLink from 'components/base/FakeLink'
import TrackPage from 'analytics/TrackPage'
import Track from 'analytics/Track'
import { openModal } from 'reducers/modals'
import { MODAL_SHARE_ANALYTICS, MODAL_TECHNICAL_DATA } from 'config/constants'
import ShareAnalytics from '../../modals/ShareAnalytics'
import TechnicalData from '../../modals/TechnicalData'
import FakeLink from '../../base/FakeLink'
import { Title, Description, FixedTopContainer, StepContainerInner } from '../helperComponents'
import OnboardingFooter from '../OnboardingFooter'

6
src/components/RecipientAddress/index.js

@ -11,6 +11,7 @@ import { radii } from 'styles/theme'
import QRCodeCameraPickerCanvas from 'components/QRCodeCameraPickerCanvas'
import Box from 'components/base/Box'
import Input from 'components/base/Input'
import { track } from 'analytics/segment'
import IconQrCode from 'icons/QrCode'
@ -64,10 +65,13 @@ class RecipientAddress extends PureComponent<Props, State> {
qrReaderOpened: false,
}
handleClickQrCode = () =>
handleClickQrCode = () => {
const { qrReaderOpened } = this.state
this.setState(prev => ({
qrReaderOpened: !prev.qrReaderOpened,
}))
!qrReaderOpened ? track('Send Flow QR Code Opened') : track('Send Flow QR Code Closed')
}
handleOnPick = (code: string) => {
const { address, ...rest } = decodeURIScheme(code)

2
src/components/SettingsPage/ReleaseNotesButton.js

@ -32,7 +32,7 @@ class ReleaseNotesButton extends PureComponent<Props> {
openModal(MODAL_RELEASES_NOTES, version)
}}
>
{t('app:settings.about.releaseNotesBtn')}
{t('app:settings.help.releaseNotesBtn')}
</Button>
)
}

4
src/components/SettingsPage/SettingsSection.js

@ -24,8 +24,8 @@ const RoundIconContainer = styled(Box).attrs({
bg: p => rgba(p.theme.colors.wallet, 0.2),
color: 'wallet',
})`
height: 30px;
width: 30px;
height: 34px;
width: 34px;
border-radius: 50%;
`

6
src/components/SettingsPage/index.js

@ -13,6 +13,7 @@ import Pills from 'components/base/Pills'
import Box from 'components/base/Box'
import SectionDisplay from './sections/Display'
import SectionCurrencies from './sections/Currencies'
import SectionHelp from './sections/Help'
import SectionAbout from './sections/About'
import SectionTools from './sections/Tools'
@ -52,6 +53,11 @@ class SettingsPage extends PureComponent<Props, State> {
label: props.t('app:settings.tabs.about'),
value: SectionAbout,
},
{
key: 'help',
label: props.t('app:settings.tabs.help'),
value: SectionHelp,
},
]
if (EXPERIMENTAL_TOOLS_SETTINGS) {

45
src/components/SettingsPage/sections/About.js

@ -2,15 +2,13 @@
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import IconHelp from 'icons/Help'
import resolveLogsDirectory from 'helpers/resolveLogsDirectory'
import { urls } from 'config/support'
import IconLoader from 'icons/Loader'
import ExportLogsBtn from 'components/ExportLogsBtn'
import CleanButton from '../CleanButton'
import ResetButton from '../ResetButton'
import ReleaseNotesButton from '../ReleaseNotesButton'
import AboutRowItem from '../AboutRowItem'
@ -25,7 +23,7 @@ type Props = {
t: T,
}
class SectionAbout extends PureComponent<Props> {
class SectionHelp extends PureComponent<Props> {
render() {
const { t } = this.props
const version = __APP_VERSION__
@ -35,44 +33,19 @@ class SectionAbout extends PureComponent<Props> {
<TrackPage category="Settings" name="About" />
<Header
icon={<IconHelp size={16} />}
icon={<IconLoader size={16} />}
title={t('app:settings.tabs.about')}
desc={t('app:settings.about.desc')}
/>
<Body>
<Row title={t('app:settings.about.version')} desc={`Ledger Live ${version}`}>
<Row title={t('app:settings.help.version')} desc={`Ledger Live ${version}`}>
<ReleaseNotesButton />
</Row>
<Row
title={t('app:settings.profile.softResetTitle')}
desc={t('app:settings.profile.softResetDesc')}
>
<CleanButton />
</Row>
<Row
title={t('app:settings.profile.hardResetTitle')}
desc={t('app:settings.profile.hardResetDesc')}
>
<ResetButton />
</Row>
<Row
title={t('app:settings.exportLogs.title')}
desc={t('app:settings.exportLogs.desc', { logsDirectory: resolveLogsDirectory() })}
>
<ExportLogsBtn />
</Row>
<AboutRowItem
title={t('app:settings.about.faq')}
desc={t('app:settings.about.faqDesc')}
url={urls.faq}
/>
<AboutRowItem
title={t('app:settings.about.terms')}
desc={t('app:settings.about.termsDesc')}
title={t('app:settings.help.terms')}
desc={t('app:settings.help.termsDesc')}
url={urls.terms}
/>
</Body>
@ -81,4 +54,4 @@ class SectionAbout extends PureComponent<Props> {
}
}
export default translate()(SectionAbout)
export default translate()(SectionHelp)

72
src/components/SettingsPage/sections/Help.js

@ -0,0 +1,72 @@
// @flow
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import IconHelp from 'icons/Help'
import resolveLogsDirectory from 'helpers/resolveLogsDirectory'
import { urls } from 'config/support'
import ExportLogsBtn from 'components/ExportLogsBtn'
import CleanButton from '../CleanButton'
import ResetButton from '../ResetButton'
import AboutRowItem from '../AboutRowItem'
import {
SettingsSection as Section,
SettingsSectionHeader as Header,
SettingsSectionBody as Body,
SettingsSectionRow as Row,
} from '../SettingsSection'
type Props = {
t: T,
}
class SectionHelp extends PureComponent<Props> {
render() {
const { t } = this.props
return (
<Section>
<TrackPage category="Settings" name="Help" />
<Header
icon={<IconHelp size={16} />}
title={t('app:settings.tabs.help')}
desc={t('app:settings.help.desc')}
/>
<Body>
<Row
title={t('app:settings.profile.softResetTitle')}
desc={t('app:settings.profile.softResetDesc')}
>
<CleanButton />
</Row>
<Row
title={t('app:settings.profile.hardResetTitle')}
desc={t('app:settings.profile.hardResetDesc')}
>
<ResetButton />
</Row>
<Row
title={t('app:settings.exportLogs.title')}
desc={t('app:settings.exportLogs.desc', { logsDirectory: resolveLogsDirectory() })}
>
<ExportLogsBtn />
</Row>
<AboutRowItem
title={t('app:settings.help.faq')}
desc={t('app:settings.help.faqDesc')}
url={urls.faq}
/>
</Body>
</Section>
)
}
}
export default translate()(SectionHelp)

2
src/components/base/LabelWithExternalIcon.js

@ -8,7 +8,7 @@ import Box from 'components/base/Box'
import IconExternalLink from 'icons/ExternalLink'
// can add more dynamic options if needed
export function LabelWithExternalIcon({ onClick, label }: { onClick: () => void, label: string }) {
export function LabelWithExternalIcon({ onClick, label }: { onClick: ?() => void, label: string }) {
return (
<LabelWrapper onClick={onClick}>
<span>{label}</span>

15
src/components/modals/AddAccounts/steps/03-step-import.js

@ -70,10 +70,21 @@ class StepImport extends PureComponent<StepProps> {
const { t } = this.props
let { name } = account
const isLegacy = name.indexOf('legacy') !== -1
const isUnsplit = name.indexOf('unsplit') !== -1
if (name === 'New Account') {
name = t('app:addAccounts.newAccount')
} else if (name.indexOf('legacy') !== -1) {
name = t('app:addAccounts.legacyAccount', { accountName: name.replace(' (legacy)', '') })
} else if (isLegacy) {
if (isUnsplit) {
name = t('app:addAccounts.legacyUnsplitAccount', {
accountName: name.replace(' (legacy)', '').replace(' (unsplit)', ''),
})
} else {
name = t('app:addAccounts.legacyAccount', { accountName: name.replace(' (legacy)', '') })
}
} else if (isUnsplit) {
name = t('app:addAccounts.unsplitAccount', { accountName: name.replace(' (unsplit)', '') })
}
return {

2
src/components/modals/Send/fields/RecipientField.js

@ -8,6 +8,7 @@ import { urls } from 'config/support'
import Box from 'components/base/Box'
import LabelWithExternalIcon from 'components/base/LabelWithExternalIcon'
import RecipientAddress from 'components/RecipientAddress'
import { track } from 'analytics/segment'
type Props<Transaction> = {
t: T,
@ -63,6 +64,7 @@ class RecipientField<Transaction> extends Component<Props<Transaction>, { isVali
handleRecipientAddressHelp = () => {
openURL(urls.recipientAddressInfo)
track('Send Flow Recipient Address Help Requested')
}
render() {
const { bridge, account, transaction, t, autoFocus } = this.props

2
src/components/modals/Send/steps/01-step-amount.js

@ -10,6 +10,7 @@ import FormattedVal from 'components/base/FormattedVal'
import Text from 'components/base/Text'
import CounterValue from 'components/CounterValue'
import Spinner from 'components/base/Spinner'
import TrackPage from 'analytics/TrackPage'
import RecipientField from '../fields/RecipientField'
import AmountField from '../fields/AmountField'
@ -34,6 +35,7 @@ export default ({
return (
<Box flow={4}>
<TrackPage category="Send Flow" name="Step 1" />
<Box flow={1}>
<Label>{t('app:send.steps.amount.selectAccountDebit')}</Label>
<SelectAccount autoFocus={!openedFromAccount} onChange={onChangeAccount} value={account} />

2
src/components/modals/Send/steps/02-step-connect-device.js

@ -11,7 +11,7 @@ import type { StepProps } from '../index'
export default function StepConnectDevice({ account, onChangeAppOpened }: StepProps<*>) {
return (
<Fragment>
<TrackPage category="Send" name="Step2" />
<TrackPage category="Send Flow" name="Step 2" />
<EnsureDeviceApp
account={account}
waitBeforeSuccess={200}

2
src/components/modals/Send/steps/03-step-verification.js

@ -33,7 +33,7 @@ export default class StepVerification extends PureComponent<StepProps<*>> {
const { t } = this.props
return (
<Container>
<TrackPage category="Send" name="Step3" />
<TrackPage category="Send Flow" name="Step 3" />
<WarnBox>{multiline(t('app:send.steps.verification.warning'))}</WarnBox>
<Info>{t('app:send.steps.verification.body')}</Info>
<DeviceConfirm />

4
src/components/modals/Send/steps/04-step-confirmation.js

@ -60,7 +60,7 @@ export default function StepConfirmation({ t, optimisticOperation, error }: Step
const translatedErrDesc = error ? <TranslatedError error={error} field="description" /> || '' : ''
return (
<Container>
<TrackPage category="Send" name="Step4" />
<TrackPage category="Send Flow" name="Step 4" />
<span style={{ color: iconColor }}>
<Icon size={43} />
</span>
@ -88,10 +88,10 @@ export function StepConfirmationFooter({
<Fragment>
<Button onClick={closeModal}>{t('app:common.close')}</Button>
{optimisticOperation ? (
// TODO: actually go to operations details
url ? (
<Button
ml={2}
event="Send Flow Step 4 View OpD Clicked"
onClick={() => {
closeModal()
if (account && optimisticOperation) {

2
src/config/constants.js

@ -88,7 +88,7 @@ export const EXPERIMENTAL_MARKET_INDICATOR_SETTINGS = boolFromEnv(
// Other constants
export const MAX_ACCOUNT_NAME_SIZE = 30
export const MAX_ACCOUNT_NAME_SIZE = 50
export const MODAL_ADD_ACCOUNTS = 'MODAL_ADD_ACCOUNTS'
export const MODAL_OPERATION_DETAILS = 'MODAL_OPERATION_DETAILS'

3
src/helpers/accountName.js

@ -6,7 +6,8 @@ export const getAccountPlaceholderName = (
c: CryptoCurrency,
index: number,
isLegacy: boolean = false,
) => `${c.name} ${index + 1}${isLegacy ? ' (legacy)' : ''}`
isUnsplit: boolean = false,
) => `${c.name} ${index + 1}${isLegacy ? ' (legacy)' : ''}${isUnsplit ? ' (unsplit)' : ''}`
export const getNewAccountPlaceholderName = getAccountPlaceholderName // same naming
// export const getNewAccountPlaceholderName = (_c: CryptoCurrency, _index: number) => `New Account`

17
src/helpers/bip32.js

@ -1,7 +1,24 @@
// @flow
import type { Account, AccountRaw } from '@ledgerhq/live-common/lib/types'
type SplitConfig = {
coinType: number,
}
export const isSegwitPath = (path: string): boolean => path.startsWith("49'")
export const isSegwitAccount = (account: Account | AccountRaw): boolean =>
isSegwitPath(account.freshAddressPath)
export const isUnsplitPath = (path: string, splitConfig: SplitConfig) => {
try {
const coinType = parseInt(path.split('/')[1], 10)
return coinType === splitConfig.coinType
} catch (e) {
return false
}
}
export const isUnsplitAccount = (account: Account | AccountRaw, splitConfig: ?SplitConfig) =>
!!splitConfig && isUnsplitPath(account.freshAddressPath, splitConfig)

86
src/helpers/libcore.js

@ -9,13 +9,23 @@ import { SHOW_LEGACY_NEW_ACCOUNT } from 'config/constants'
import type { AccountRaw, OperationRaw, OperationType } from '@ledgerhq/live-common/lib/types'
import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgercore_doc'
import { isSegwitAccount } from 'helpers/bip32'
import { isSegwitAccount, isUnsplitAccount } from 'helpers/bip32'
import * as accountIdHelper from 'helpers/accountId'
import { createCustomErrorClass, deserializeError } from './errors'
import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName'
const NoAddressesFound = createCustomErrorClass('NoAddressesFound')
// TODO: put that info inside currency itself
const SPLITTED_CURRENCIES = {
bitcoin_cash: {
coinType: 0,
},
bitcoin_gold: {
coinType: 0,
},
}
export function isValidAddress(core: *, currency: *, address: string): boolean {
const addr = new core.NJSAddress(address, currency)
return addr.isValid(address, currency)
@ -48,6 +58,7 @@ export function scanAccountsOnDevice(props: Props): Promise<AccountRaw[]> {
...commonParams,
showNewAccount: !!SHOW_LEGACY_NEW_ACCOUNT || !currency.supportsSegwit,
isSegwit: false,
isUnsplit: false,
})
allAccounts = allAccounts.concat(nonSegwitAccounts)
@ -56,10 +67,32 @@ export function scanAccountsOnDevice(props: Props): Promise<AccountRaw[]> {
...commonParams,
showNewAccount: true,
isSegwit: true,
isUnsplit: false,
})
allAccounts = allAccounts.concat(segwitAccounts)
}
// TODO: put that info inside currency itself
if (currencyId in SPLITTED_CURRENCIES) {
const splittedAccounts = await scanAccountsOnDeviceBySegwit({
...commonParams,
isSegwit: false,
showNewAccount: false,
isUnsplit: true,
})
allAccounts = allAccounts.concat(splittedAccounts)
if (currency.supportsSegwit) {
const segwitAccounts = await scanAccountsOnDeviceBySegwit({
...commonParams,
showNewAccount: false,
isUnsplit: true,
isSegwit: true,
})
allAccounts = allAccounts.concat(segwitAccounts)
}
}
return allAccounts
})
}
@ -68,12 +101,15 @@ function encodeWalletName({
publicKey,
currencyId,
isSegwit,
isUnsplit,
}: {
publicKey: string,
currencyId: string,
isSegwit: boolean,
isUnsplit: boolean,
}) {
return `${publicKey}__${currencyId}${isSegwit ? '_segwit' : ''}`
const splitConfig = isUnsplit ? SPLITTED_CURRENCIES[currencyId] || null : null
return `${publicKey}__${currencyId}${isSegwit ? '_segwit' : ''}${splitConfig ? '_unsplit' : ''}`
}
async function scanAccountsOnDeviceBySegwit({
@ -82,6 +118,7 @@ async function scanAccountsOnDeviceBySegwit({
currencyId,
onAccountScanned,
isSegwit,
isUnsplit,
showNewAccount,
}: {
core: *,
@ -90,17 +127,20 @@ async function scanAccountsOnDeviceBySegwit({
onAccountScanned: AccountRaw => void,
isSegwit: boolean, // FIXME all segwit to change to 'purpose'
showNewAccount: boolean,
isUnsplit: boolean,
}): Promise<AccountRaw[]> {
const { coinType } = getCryptoCurrencyById(currencyId)
const { publicKey } = await hwApp.getWalletPublicKey(
`${isSegwit ? '49' : '44'}'/${coinType}'`,
false,
isSegwit,
)
const walletName = encodeWalletName({ publicKey, currencyId, isSegwit })
const customOpts =
isUnsplit && SPLITTED_CURRENCIES[currencyId] ? SPLITTED_CURRENCIES[currencyId] : null
const { coinType } = customOpts ? customOpts.coinType : getCryptoCurrencyById(currencyId)
const path = `${isSegwit ? '49' : '44'}'/${coinType}'`
const { publicKey } = await hwApp.getWalletPublicKey(path, false, isSegwit)
const walletName = encodeWalletName({ publicKey, currencyId, isSegwit, isUnsplit })
// retrieve or create the wallet
const wallet = await getOrCreateWallet(core, walletName, currencyId, isSegwit)
const wallet = await getOrCreateWallet(core, walletName, currencyId, isSegwit, isUnsplit)
const accountsCount = await wallet.getAccountCount()
// recursively scan all accounts on device on the given app
@ -115,6 +155,7 @@ async function scanAccountsOnDeviceBySegwit({
accounts: [],
onAccountScanned,
isSegwit,
isUnsplit,
showNewAccount,
})
@ -188,6 +229,7 @@ async function scanNextAccount(props: {
accounts: AccountRaw[],
onAccountScanned: AccountRaw => void,
isSegwit: boolean,
isUnsplit: boolean,
showNewAccount: boolean,
}): Promise<AccountRaw[]> {
const {
@ -200,6 +242,7 @@ async function scanNextAccount(props: {
accounts,
onAccountScanned,
isSegwit,
isUnsplit,
showNewAccount,
} = props
@ -222,6 +265,7 @@ async function scanNextAccount(props: {
const account = await buildAccountRaw({
njsAccount,
isSegwit,
isUnsplit,
accountIndex,
wallet,
currencyId,
@ -259,6 +303,7 @@ async function getOrCreateWallet(
WALLET_IDENTIFIER: string,
currencyId: string,
isSegwit: boolean,
isUnsplit: boolean,
): NJSWallet {
const pool = core.getPoolInstance()
try {
@ -266,12 +311,18 @@ async function getOrCreateWallet(
return wallet
} catch (err) {
const currency = await pool.getCurrency(currencyId)
const splitConfig = isUnsplit ? SPLITTED_CURRENCIES[currencyId] || null : null
const coinType = splitConfig ? splitConfig.coinType : '<coin_type>'
const walletConfig = isSegwit
? {
KEYCHAIN_ENGINE: 'BIP49_P2SH',
KEYCHAIN_DERIVATION_SCHEME: "49'/<coin_type>'/<account>'/<node>/<address>",
KEYCHAIN_DERIVATION_SCHEME: `49'/${coinType}'/<account>'/<node>/<address>`,
}
: undefined
: splitConfig
? {
KEYCHAIN_DERIVATION_SCHEME: `44'/${coinType}'/<account>'/<node>/<address>`,
}
: undefined
const njsWalletConfig = createWalletConfig(core, walletConfig)
const wallet = await core
.getPoolInstance()
@ -283,6 +334,7 @@ async function getOrCreateWallet(
async function buildAccountRaw({
njsAccount,
isSegwit,
isUnsplit,
wallet,
currencyId,
core,
@ -291,6 +343,7 @@ async function buildAccountRaw({
}: {
njsAccount: NJSAccount,
isSegwit: boolean,
isUnsplit: boolean,
wallet: NJSWallet,
currencyId: string,
accountIndex: number,
@ -336,6 +389,7 @@ async function buildAccountRaw({
currency,
accountIndex,
(currency.supportsSegwit && !isSegwit) || false,
isUnsplit,
)
const rawAccount: AccountRaw = {
@ -411,6 +465,8 @@ function buildOperationRaw({
export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: AccountRaw }) {
const decodedAccountId = accountIdHelper.decode(rawAccount.id)
const isSegwit = isSegwitAccount(rawAccount)
const isUnsplit = isUnsplitAccount(rawAccount, SPLITTED_CURRENCIES[rawAccount.currencyId])
let njsWallet
try {
njsWallet = await core.getPoolInstance().getWallet(decodedAccountId.walletName)
@ -420,7 +476,8 @@ export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: A
core,
decodedAccountId.walletName,
rawAccount.currencyId,
isSegwitAccount(rawAccount),
isSegwit,
isUnsplit,
)
}
@ -443,7 +500,8 @@ export async function syncAccount({ rawAccount, core }: { core: *, rawAccount: A
const syncedRawAccount = await buildAccountRaw({
njsAccount,
isSegwit: isSegwitAccount(rawAccount),
isSegwit,
isUnsplit,
accountIndex: rawAccount.index,
wallet: njsWallet,
currencyId: rawAccount.currencyId,

2
src/icons/Currencies.js

@ -5,7 +5,7 @@ import React from 'react'
const path = (
<path
fill="currentColor"
d="M8 5.581c-3.865 0-7-1.083-7-2.79C1 1.083 4.135 0 8 0s7 1.083 7 2.79c0 1.708-3.135 2.791-7 2.791zm-7-2.79c0-.309.241-.558.538-.558.298 0 .539.25.539.558v10.418c0 .28.516.704 1.517 1.05 1.142.396 2.714.625 4.406.625 1.692 0 3.264-.23 4.406-.625 1.001-.346 1.517-.77 1.517-1.05V2.791c0-.309.241-.558.539-.558.297 0 .538.25.538.558v10.418C15 14.921 11.88 16 8 16s-7-1.08-7-2.79V2.79zM13.923 8c0-.308.241-.558.539-.558.297 0 .538.25.538.558 0 1.711-3.12 2.79-7 2.79S1 9.712 1 8c0-.308.241-.558.538-.558.298 0 .539.25.539.558 0 .28.516.704 1.517 1.05 1.142.395 2.714.624 4.406.624 1.692 0 3.264-.229 4.406-.624 1.001-.346 1.517-.77 1.517-1.05zM8 4.465c1.682 0 3.254-.23 4.399-.625 1.004-.347 1.524-.772 1.524-1.05 0-.277-.52-.702-1.524-1.048-1.145-.396-2.717-.626-4.399-.626s-3.254.23-4.399.626c-1.004.346-1.524.771-1.524 1.049 0 .277.52.702 1.524 1.049 1.145.395 2.717.625 4.399.625z"
d="M2.75,5.15508428 L2.75,8.00000015 C2.75,8.47106568 5.0832134,9.25 8,9.25 C10.9167866,9.25 13.25,8.47106568 13.25,8 L13.25,5.15508428 C12.0122098,5.75709992 10.119699,6.08333333 8,6.08333333 C5.88030098,6.08333333 3.98779024,5.75709992 2.75,5.15508428 Z M1.25,3.33333333 C1.25,1.59133795 4.27401384,0.583333333 8,0.583333333 C11.7259862,0.583333333 14.75,1.59133795 14.75,3.33333333 L14.75,12.6666667 C14.75,14.4120112 11.7406635,15.4166667 8,15.4166667 C4.25933652,15.4166667 1.25,14.4120112 1.25,12.6666667 L1.25,3.33333333 Z M13.25,9.82541772 C12.0146597,10.4256664 10.1253772,10.75 8,10.75 C5.87462278,10.75 3.98534032,10.4256664 2.75,9.82541772 L2.75,12.6666667 C2.75,13.1377323 5.0832134,13.9166667 8,13.9166667 C10.9167866,13.9166667 13.25,13.1377323 13.25,12.6666667 L13.25,9.82541772 Z M8,4.58333333 C10.9014308,4.58333333 13.25,3.80047695 13.25,3.33333333 C13.25,2.86618972 10.9014308,2.08333333 8,2.08333333 C5.09856916,2.08333333 2.75,2.86618972 2.75,3.33333333 C2.75,3.80047695 5.09856916,4.58333333 8,4.58333333 Z"
/>
)

2
src/icons/Display.js

@ -5,7 +5,7 @@ import React from 'react'
const path = (
<path
fill="currentColor"
d="M3.2 2.26c-.552 0-1 .471-1 1.051v6.547c0 .58.448 1.05 1 1.05h9.6c.552 0 1-.47 1-1.05V3.31c0-.58-.448-1.05-1-1.05H3.2zm5.4 9.909v1.57h1.96c.331 0 .6.283.6.63 0 .349-.269.631-.6.631H5.44c-.331 0-.6-.282-.6-.63 0-.348.269-.63.6-.63H7.4v-1.571H3.2c-1.215 0-2.2-1.035-2.2-2.311V3.31C1 2.035 1.985 1 3.2 1h9.6C14.015 1 15 2.035 15 3.311v6.547c0 1.276-.985 2.311-2.2 2.311H8.6z"
d="M3.33333333,2.75 C2.6429774,2.75 2.08333333,3.30964406 2.08333333,4 L2.08333333,9.33333333 C2.08333333,10.0236893 2.6429774,10.5833333 3.33333333,10.5833333 L12.6666667,10.5833333 C13.3570226,10.5833333 13.9166667,10.0236893 13.9166667,9.33333333 L13.9166667,4 C13.9166667,3.30964406 13.3570226,2.75 12.6666667,2.75 L3.33333333,2.75 Z M8.75,12.0833333 L8.75,13.25 L10.6666667,13.25 C11.0808802,13.25 11.4166667,13.5857864 11.4166667,14 C11.4166667,14.4142136 11.0808802,14.75 10.6666667,14.75 L5.33333333,14.75 C4.91911977,14.75 4.58333333,14.4142136 4.58333333,14 C4.58333333,13.5857864 4.91911977,13.25 5.33333333,13.25 L7.25,13.25 L7.25,12.0833333 L3.33333333,12.0833333 C1.81455027,12.0833333 0.583333333,10.8521164 0.583333333,9.33333333 L0.583333333,4 C0.583333333,2.48121694 1.81455027,1.25 3.33333333,1.25 L12.6666667,1.25 C14.1854497,1.25 15.4166667,2.48121694 15.4166667,4 L15.4166667,9.33333333 C15.4166667,10.8521164 14.1854497,12.0833333 12.6666667,12.0833333 L8.75,12.0833333 Z"
/>
)

2
src/icons/Help.js

@ -5,7 +5,7 @@ import React from 'react'
const path = (
<path
fill="currentColor"
d="M3.3 4.007a6.167 6.167 0 1 0 .707-.707l2.135 2.135a3.167 3.167 0 1 1-.707.707L3.3 4.007zM8 15.167A7.167 7.167 0 1 1 8 .833a7.167 7.167 0 0 1 0 14.334zm0-5a2.167 2.167 0 1 0 0-4.334 2.167 2.167 0 0 0 0 4.334zm1.387-4.054c0-.128.048-.256.146-.353l2.353-2.354a.5.5 0 0 1 .708.708L10.24 6.467a.5.5 0 0 1-.707-.707l2.827-2.827a.5.5 0 0 1 .707.707L10.24 6.467a.5.5 0 0 1-.853-.354zm.146 4.127a.5.5 0 0 1 .707-.707l2.827 2.827a.5.5 0 1 1-.707.707L9.533 10.24zM3.64 13.067a.5.5 0 1 1-.707-.707L5.76 9.533a.5.5 0 1 1 .707.707L3.64 13.067z"
d="M8,15.4166667 C3.90388811,15.4166667 0.583333333,12.0961119 0.583333333,8 C0.583333333,3.90388811 3.90388811,0.583333333 8,0.583333333 C12.0961119,0.583333333 15.4166667,3.90388811 15.4166667,8 C15.4166667,12.0961119 12.0961119,15.4166667 8,15.4166667 Z M8,13.9166667 C11.2676848,13.9166667 13.9166667,11.2676848 13.9166667,8 C13.9166667,4.73231523 11.2676848,2.08333333 8,2.08333333 C4.73231523,2.08333333 2.08333333,4.73231523 2.08333333,8 C2.08333333,11.2676848 4.73231523,13.9166667 8,13.9166667 Z M8,11.4166667 C6.1130271,11.4166667 4.58333333,9.8869729 4.58333333,8 C4.58333333,6.1130271 6.1130271,4.58333333 8,4.58333333 C9.8869729,4.58333333 11.4166667,6.1130271 11.4166667,8 C11.4166667,9.8869729 9.8869729,11.4166667 8,11.4166667 Z M8,9.91666667 C9.05854577,9.91666667 9.91666667,9.05854577 9.91666667,8 C9.91666667,6.94145423 9.05854577,6.08333333 8,6.08333333 C6.94145423,6.08333333 6.08333333,6.94145423 6.08333333,8 C6.08333333,9.05854577 6.94145423,9.91666667 8,9.91666667 Z M2.75633658,3.81699675 C2.46344336,3.52410353 2.46344336,3.0492298 2.75633658,2.75633658 C3.0492298,2.46344336 3.52410353,2.46344336 3.81699675,2.75633658 L6.64366342,5.58300325 C6.93655664,5.87589647 6.93655664,6.3507702 6.64366342,6.64366342 C6.3507702,6.93655664 5.87589647,6.93655664 5.58300325,6.64366342 L2.75633658,3.81699675 Z M9.35633658,10.4169968 C9.06344336,10.1241035 9.06344336,9.6492298 9.35633658,9.35633658 C9.6492298,9.06344336 10.1241035,9.06344336 10.4169968,9.35633658 L13.2436634,12.1830032 C13.5365566,12.4758965 13.5365566,12.9507702 13.2436634,13.2436634 C12.9507702,13.5365566 12.4758965,13.5365566 12.1830032,13.2436634 L9.35633658,10.4169968 Z M10.4169968,6.64366342 C10.1241035,6.93655664 9.6492298,6.93655664 9.35633658,6.64366342 C9.06344336,6.3507702 9.06344336,5.87589647 9.35633658,5.58300325 L11.7096699,3.22966991 C12.0025631,2.9367767 12.4774369,2.9367767 12.7703301,3.22966991 C13.0632233,3.52256313 13.0632233,3.99743687 12.7703301,4.29033009 L10.4169968,6.64366342 Z M3.81699675,13.2436634 C3.52410353,13.5365566 3.0492298,13.5365566 2.75633658,13.2436634 C2.46344336,12.9507702 2.46344336,12.4758965 2.75633658,12.1830032 L5.58300325,9.35633658 C5.87589647,9.06344336 6.3507702,9.06344336 6.64366342,9.35633658 C6.93655664,9.6492298 6.93655664,10.1241035 6.64366342,10.4169968 L3.81699675,13.2436634 Z"
/>
)

7
static/i18n/en/app.yml

@ -169,6 +169,8 @@ addAccounts:
editName: Edit name
newAccount: New account
legacyAccount: '{{accountName}} (legacy)'
legacyUnsplitAccount: '{{accountName}} (legacy) (unsplit)'
unsplitAccount: '{{accountName}} (unsplit)'
noAccountToImport: No existing {{currencyName}} accounts to add
success: Account successfully added to your portfolio
success_plural: Accounts successfully added to your portfolio
@ -310,7 +312,8 @@ settings: # Always ensure descriptions carry full stops (.)
display: General
currencies: Currencies
profile: Profile
about: Help
help: Help
about: About
display:
desc: Change settings that affect Ledger Live in general.
language: Display language
@ -355,6 +358,8 @@ settings: # Always ensure descriptions carry full stops (.)
reportErrors: Report bugs
reportErrorsDesc: Share anonymous usage and diagnostics data to help improve Ledger products, services and security features.
about:
desc: Learn about Ledger Live features
help:
desc: Learn about Ledger Live features or get help.
version: Version
releaseNotesBtn: Details # Close button instead of continue.

35
static/i18n/fr/app.yml

@ -8,7 +8,9 @@ common:
cancel: Cancel
delete: Delete
continue: Continue
learnMore: Learn More
skipThisStep: Skip this step
needHelp: Need help?
chooseWalletPlaceholder: Choose a wallet...
currency: Currency
selectAccount: Select an account
@ -37,8 +39,9 @@ common:
reverify: Re-verify
verify: Verify
copy: Copy
copied: Copied!
addressCopied: Address copied!
copyAddress: Copy address
copied: Copied
addressCopied: Address copied
lockScreen:
title: Welcome back
subTitle:
@ -55,6 +58,8 @@ common:
error:
load: Unable to load
noResults: No results
buttons:
displayAddressOnDevice: Verify
operation:
type:
IN: Received
@ -119,7 +124,9 @@ dashboard:
currentAddress:
title: Current address
for: Address for account <1><0>{{accountName}}</0></1>
message: Your receive address has not been confirmed on your Ledger device. Please verify the address for optimal security.
messageIfUnverified: Verify the address on your device for optimal security. Press the right button to confirm.
messageIfAccepted: "{{currencyName}} address confirmed on your device. Carefully verify when you copy and paste it."
messageIfSkipped: 'Your receive address has not been confirmed on your Ledger device. Please verify your {{currencyName}} address for optimal security.'
deviceConnect:
step1:
choose: "We detected {{count}} connected devices, please select one:"
@ -151,7 +158,7 @@ addAccounts:
connectDevice: Device
import: Accounts
finish: Confirmation
accountAlreadyImportedSubtitle: '{{count}} Already in portfolio'
accountAlreadyImportedSubtitle: 'Accounts already in portfolio ({{count}})'
accountToImportSubtitle: 'Add existing account'
accountToImportSubtitle_plural: 'Add existing accounts'
selectAll: Select all ({{count}})
@ -159,14 +166,16 @@ addAccounts:
editName: Edit name
newAccount: New account
legacyAccount: '{{accountName}} (legacy)'
legacyUnsplitAccount: '{{accountName}} (legacy) (unsplit)'
unsplitAccount: '{{accountName}} (unsplit)'
noAccountToImport: No existing {{currencyName}} accounts to add
success: Account successfully added to your portfolio
success_plural: Accounts successfully added to your portfolio
successDescription: Your account have been created.
successDescription: Your account has been created.
successDescription_plural: Your accounts have been created.
createNewAccount:
title: Add a new account
noOperationOnLastAccount: 'You have to receive crypto assets on <1><0>{{accountName}}</0></1> before you can create a new account.'
noOperationOnLastAccount: "No transactions found on your last new account <1><0>{{accountName}}</0></1>. You can add a new account after you've started transacting on that account."
noAccountToCreate: No <1><0>{{currencyName}}</0></1> account was found to create
somethingWentWrong: Something went wrong during synchronization, please try again.
cta:
@ -217,12 +226,15 @@ manager:
idCheck: Identifier check
updateMCU: Update MCU
confirm: Confirmation
installing: Installing firmware
flashing: Installing MCU
confirmIdentifier: Confirm identifier
confirmIdentifierText: Please confirm identifier on your Device. Be sure the identifier is the same as below
identifier: Identifier
mcuTitle: Updating MCU
mcuFirst: Unplug your device from your computer
mcuSecond: Press and hold left button and plug your device until the processing screen appears
mcuPin: If asked on device, please enter your pin code to finish the process
successTitle: Firmware has been updated with success
successText: You can now re-install your applications on your device
title: Manager
@ -246,8 +258,8 @@ receive:
withoutDevice: Don't have your device?
confirmAddress:
title: Verification
action: Confirm address on device
text: Verify that the address below matches the address displayed on your device
action: Verify address on device
text: A {{currencyName}} receive address will be displayed on your device. Carefully verify that it matches the address on your computer.
support: Contact us
receiveFunds:
title: Receive
@ -298,7 +310,8 @@ settings:
display: General
currencies: Currencies
profile: Profile
about: Help
help: Help
about: About
display:
desc: Change settings that affect Ledger Live in general.
language: Display language
@ -343,6 +356,8 @@ settings:
reportErrors: Report bugs
reportErrorsDesc: Share anonymous usage and diagnostics data to help improve Ledger products, services and security features.
about:
desc: Learn about Ledger Live features
help:
desc: Learn about Ledger Live features or get help.
version: Version
releaseNotesBtn: Details
@ -408,5 +423,5 @@ crash:
disclaimerModal:
title: Trade safely
desc_1: Before sending and receiving crypto assets, educate yourself to make informed decisions. Crypto assets are volatile and the prices can go up and down. Carefully evaluate your trading goals and the financial risk you are willing to take.
desc_2: Please beware that Ledger does not provide financial, tax, or legal advice. You should take such decisions on your own or rely on opinions of reliable experts.
desc_2: Please beware that Ledger does not provide financial, tax, or legal advice. You should take such decisions on your own or consult with reliable experts.
cta: Got it

8
static/i18n/fr/errors.yml

@ -20,6 +20,12 @@ DisconnectedDevice:
Error:
title: '{{message}}'
description: Something went wrong. Please retry or contact us.
TypeError:
title: '{{message}}'
description: Something went wrong. Please retry or contact us.
ReferenceError:
title: '{{message}}'
description: Something went wrong. Please retry or contact us.
FeeEstimationFailed:
title: Sorry, fee estimation failed
description: 'Try setting a custom fee (status: {{status}})'
@ -88,7 +94,7 @@ UserRefusedOnDevice:
description: Please retry or contact Ledger Support in case of doubt.
UserRefusedAddress:
title: Receive address rejected
description: Please try again or contact Support
description: Please try again or contact Ledger Support
WebsocketConnectionError:
title: Sorry, try again (websocket error).
description:

26
static/i18n/fr/onboarding.yml

@ -130,11 +130,33 @@ analytics:
title: Analytics and bug reports
desc: Share anonymous usage and diagnostics data to help improve Ledger products, services and security features.
shareAnalytics:
title: Share usage data
desc: Enable analytics of anonymous data to help Ledger improve the user experience. This includes the operating system, language, firmware versions and the number of added accounts.
title: Share analytics
desc: Enable analytics to help Ledger understand how to improve the user experience.
mandatoryContextual:
item1: Page visits
item2: Actions (send, receive, logout)
item3: Clicks
item4: Redirections to webpages
item5: Scrolled to end of page
item6: Install/Uninstall
item7: Number of accounts, currencies and operations
item8: Overall and page session duration
item9: Device product ID
item10: Device firmware and app versions
sentryLogs:
title: Report bugs
desc: Automatically send reports to help Ledger fix bugs
technicalData:
title: Technical data *
desc: Ledger will automatically collect technical information to get basic feedback on usage. This information is anonymous and does not contain personal data.
mandatoryText: '* mandatory'
mandatoryContextual:
title: Technical data
item1: Anonymous unique application ID
item2: OS name and version
item3: Ledger Live version
item4: Application language or region
item5: OS Language/Region
finish:
title: Your device is ready!
desc: Proceed to your portfolio and start adding your accounts...

Loading…
Cancel
Save