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 { translate } from 'react-i18next'
import { openURL } from 'helpers/linking' import { openURL } from 'helpers/linking'
import { urls } from 'config/support' import { urls } from 'config/support'
import { track } from 'analytics/segment'
export default translate()(({ children, t }: { children: React$Node, t: * }) => ( export default translate()(({ children, t }: { children: React$Node, t: * }) => (
<Box flow={1}> <Box flow={1}>
<LabelWithExternalIcon <LabelWithExternalIcon
onClick={() => openURL(urls.feesMoreInfo)} onClick={() => {
openURL(urls.feesMoreInfo)
track('Send Flow Fees Help Requested')
}}
label={t('app:send.steps.amount.fees')} label={t('app:send.steps.amount.fees')}
/> />
<Box horizontal flow={5}> <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 { saveSettings } from 'actions/settings'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Switch from 'components/base/Switch' import Switch from 'components/base/Switch'
import FakeLink from 'components/base/FakeLink'
import TrackPage from 'analytics/TrackPage' import TrackPage from 'analytics/TrackPage'
import Track from 'analytics/Track' import Track from 'analytics/Track'
import { openModal } from 'reducers/modals' import { openModal } from 'reducers/modals'
import { MODAL_SHARE_ANALYTICS, MODAL_TECHNICAL_DATA } from 'config/constants' import { MODAL_SHARE_ANALYTICS, MODAL_TECHNICAL_DATA } from 'config/constants'
import ShareAnalytics from '../../modals/ShareAnalytics' import ShareAnalytics from '../../modals/ShareAnalytics'
import TechnicalData from '../../modals/TechnicalData' import TechnicalData from '../../modals/TechnicalData'
import FakeLink from '../../base/FakeLink'
import { Title, Description, FixedTopContainer, StepContainerInner } from '../helperComponents' import { Title, Description, FixedTopContainer, StepContainerInner } from '../helperComponents'
import OnboardingFooter from '../OnboardingFooter' 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 QRCodeCameraPickerCanvas from 'components/QRCodeCameraPickerCanvas'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Input from 'components/base/Input' import Input from 'components/base/Input'
import { track } from 'analytics/segment'
import IconQrCode from 'icons/QrCode' import IconQrCode from 'icons/QrCode'
@ -64,10 +65,13 @@ class RecipientAddress extends PureComponent<Props, State> {
qrReaderOpened: false, qrReaderOpened: false,
} }
handleClickQrCode = () => handleClickQrCode = () => {
const { qrReaderOpened } = this.state
this.setState(prev => ({ this.setState(prev => ({
qrReaderOpened: !prev.qrReaderOpened, qrReaderOpened: !prev.qrReaderOpened,
})) }))
!qrReaderOpened ? track('Send Flow QR Code Opened') : track('Send Flow QR Code Closed')
}
handleOnPick = (code: string) => { handleOnPick = (code: string) => {
const { address, ...rest } = decodeURIScheme(code) 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) openModal(MODAL_RELEASES_NOTES, version)
}} }}
> >
{t('app:settings.about.releaseNotesBtn')} {t('app:settings.help.releaseNotesBtn')}
</Button> </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), bg: p => rgba(p.theme.colors.wallet, 0.2),
color: 'wallet', color: 'wallet',
})` })`
height: 30px; height: 34px;
width: 30px; width: 34px;
border-radius: 50%; 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 Box from 'components/base/Box'
import SectionDisplay from './sections/Display' import SectionDisplay from './sections/Display'
import SectionCurrencies from './sections/Currencies' import SectionCurrencies from './sections/Currencies'
import SectionHelp from './sections/Help'
import SectionAbout from './sections/About' import SectionAbout from './sections/About'
import SectionTools from './sections/Tools' import SectionTools from './sections/Tools'
@ -52,6 +53,11 @@ class SettingsPage extends PureComponent<Props, State> {
label: props.t('app:settings.tabs.about'), label: props.t('app:settings.tabs.about'),
value: SectionAbout, value: SectionAbout,
}, },
{
key: 'help',
label: props.t('app:settings.tabs.help'),
value: SectionHelp,
},
] ]
if (EXPERIMENTAL_TOOLS_SETTINGS) { if (EXPERIMENTAL_TOOLS_SETTINGS) {

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

@ -2,15 +2,13 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import type { T } from 'types/common' import type { T } from 'types/common'
import TrackPage from 'analytics/TrackPage' import TrackPage from 'analytics/TrackPage'
import IconHelp from 'icons/Help'
import resolveLogsDirectory from 'helpers/resolveLogsDirectory'
import { urls } from 'config/support' 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 ReleaseNotesButton from '../ReleaseNotesButton'
import AboutRowItem from '../AboutRowItem' import AboutRowItem from '../AboutRowItem'
@ -25,7 +23,7 @@ type Props = {
t: T, t: T,
} }
class SectionAbout extends PureComponent<Props> { class SectionHelp extends PureComponent<Props> {
render() { render() {
const { t } = this.props const { t } = this.props
const version = __APP_VERSION__ const version = __APP_VERSION__
@ -35,44 +33,19 @@ class SectionAbout extends PureComponent<Props> {
<TrackPage category="Settings" name="About" /> <TrackPage category="Settings" name="About" />
<Header <Header
icon={<IconHelp size={16} />} icon={<IconLoader size={16} />}
title={t('app:settings.tabs.about')} title={t('app:settings.tabs.about')}
desc={t('app:settings.about.desc')} desc={t('app:settings.about.desc')}
/> />
<Body> <Body>
<Row title={t('app:settings.about.version')} desc={`Ledger Live ${version}`}> <Row title={t('app:settings.help.version')} desc={`Ledger Live ${version}`}>
<ReleaseNotesButton /> <ReleaseNotesButton />
</Row> </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 <AboutRowItem
title={t('app:settings.about.terms')} title={t('app:settings.help.terms')}
desc={t('app:settings.about.termsDesc')} desc={t('app:settings.help.termsDesc')}
url={urls.terms} url={urls.terms}
/> />
</Body> </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' import IconExternalLink from 'icons/ExternalLink'
// can add more dynamic options if needed // 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 ( return (
<LabelWrapper onClick={onClick}> <LabelWrapper onClick={onClick}>
<span>{label}</span> <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 const { t } = this.props
let { name } = account let { name } = account
const isLegacy = name.indexOf('legacy') !== -1
const isUnsplit = name.indexOf('unsplit') !== -1
if (name === 'New Account') { if (name === 'New Account') {
name = t('app:addAccounts.newAccount') name = t('app:addAccounts.newAccount')
} else if (name.indexOf('legacy') !== -1) { } else if (isLegacy) {
name = t('app:addAccounts.legacyAccount', { accountName: name.replace(' (legacy)', '') }) 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 { 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 Box from 'components/base/Box'
import LabelWithExternalIcon from 'components/base/LabelWithExternalIcon' import LabelWithExternalIcon from 'components/base/LabelWithExternalIcon'
import RecipientAddress from 'components/RecipientAddress' import RecipientAddress from 'components/RecipientAddress'
import { track } from 'analytics/segment'
type Props<Transaction> = { type Props<Transaction> = {
t: T, t: T,
@ -63,6 +64,7 @@ class RecipientField<Transaction> extends Component<Props<Transaction>, { isVali
handleRecipientAddressHelp = () => { handleRecipientAddressHelp = () => {
openURL(urls.recipientAddressInfo) openURL(urls.recipientAddressInfo)
track('Send Flow Recipient Address Help Requested')
} }
render() { render() {
const { bridge, account, transaction, t, autoFocus } = this.props 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 Text from 'components/base/Text'
import CounterValue from 'components/CounterValue' import CounterValue from 'components/CounterValue'
import Spinner from 'components/base/Spinner' import Spinner from 'components/base/Spinner'
import TrackPage from 'analytics/TrackPage'
import RecipientField from '../fields/RecipientField' import RecipientField from '../fields/RecipientField'
import AmountField from '../fields/AmountField' import AmountField from '../fields/AmountField'
@ -34,6 +35,7 @@ export default ({
return ( return (
<Box flow={4}> <Box flow={4}>
<TrackPage category="Send Flow" name="Step 1" />
<Box flow={1}> <Box flow={1}>
<Label>{t('app:send.steps.amount.selectAccountDebit')}</Label> <Label>{t('app:send.steps.amount.selectAccountDebit')}</Label>
<SelectAccount autoFocus={!openedFromAccount} onChange={onChangeAccount} value={account} /> <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<*>) { export default function StepConnectDevice({ account, onChangeAppOpened }: StepProps<*>) {
return ( return (
<Fragment> <Fragment>
<TrackPage category="Send" name="Step2" /> <TrackPage category="Send Flow" name="Step 2" />
<EnsureDeviceApp <EnsureDeviceApp
account={account} account={account}
waitBeforeSuccess={200} 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 const { t } = this.props
return ( return (
<Container> <Container>
<TrackPage category="Send" name="Step3" /> <TrackPage category="Send Flow" name="Step 3" />
<WarnBox>{multiline(t('app:send.steps.verification.warning'))}</WarnBox> <WarnBox>{multiline(t('app:send.steps.verification.warning'))}</WarnBox>
<Info>{t('app:send.steps.verification.body')}</Info> <Info>{t('app:send.steps.verification.body')}</Info>
<DeviceConfirm /> <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" /> || '' : '' const translatedErrDesc = error ? <TranslatedError error={error} field="description" /> || '' : ''
return ( return (
<Container> <Container>
<TrackPage category="Send" name="Step4" /> <TrackPage category="Send Flow" name="Step 4" />
<span style={{ color: iconColor }}> <span style={{ color: iconColor }}>
<Icon size={43} /> <Icon size={43} />
</span> </span>
@ -88,10 +88,10 @@ export function StepConfirmationFooter({
<Fragment> <Fragment>
<Button onClick={closeModal}>{t('app:common.close')}</Button> <Button onClick={closeModal}>{t('app:common.close')}</Button>
{optimisticOperation ? ( {optimisticOperation ? (
// TODO: actually go to operations details
url ? ( url ? (
<Button <Button
ml={2} ml={2}
event="Send Flow Step 4 View OpD Clicked"
onClick={() => { onClick={() => {
closeModal() closeModal()
if (account && optimisticOperation) { if (account && optimisticOperation) {

2
src/config/constants.js

@ -88,7 +88,7 @@ export const EXPERIMENTAL_MARKET_INDICATOR_SETTINGS = boolFromEnv(
// Other constants // 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_ADD_ACCOUNTS = 'MODAL_ADD_ACCOUNTS'
export const MODAL_OPERATION_DETAILS = 'MODAL_OPERATION_DETAILS' export const MODAL_OPERATION_DETAILS = 'MODAL_OPERATION_DETAILS'

3
src/helpers/accountName.js

@ -6,7 +6,8 @@ export const getAccountPlaceholderName = (
c: CryptoCurrency, c: CryptoCurrency,
index: number, index: number,
isLegacy: boolean = false, 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 = getAccountPlaceholderName // same naming
// export const getNewAccountPlaceholderName = (_c: CryptoCurrency, _index: number) => `New Account` // export const getNewAccountPlaceholderName = (_c: CryptoCurrency, _index: number) => `New Account`

17
src/helpers/bip32.js

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

2
src/icons/Currencies.js

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

35
static/i18n/fr/app.yml

@ -8,7 +8,9 @@ common:
cancel: Cancel cancel: Cancel
delete: Delete delete: Delete
continue: Continue continue: Continue
learnMore: Learn More
skipThisStep: Skip this step skipThisStep: Skip this step
needHelp: Need help?
chooseWalletPlaceholder: Choose a wallet... chooseWalletPlaceholder: Choose a wallet...
currency: Currency currency: Currency
selectAccount: Select an account selectAccount: Select an account
@ -37,8 +39,9 @@ common:
reverify: Re-verify reverify: Re-verify
verify: Verify verify: Verify
copy: Copy copy: Copy
copied: Copied! copyAddress: Copy address
addressCopied: Address copied! copied: Copied
addressCopied: Address copied
lockScreen: lockScreen:
title: Welcome back title: Welcome back
subTitle: subTitle:
@ -55,6 +58,8 @@ common:
error: error:
load: Unable to load load: Unable to load
noResults: No results noResults: No results
buttons:
displayAddressOnDevice: Verify
operation: operation:
type: type:
IN: Received IN: Received
@ -119,7 +124,9 @@ dashboard:
currentAddress: currentAddress:
title: Current address title: Current address
for: Address for account <1><0>{{accountName}}</0></1> 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: deviceConnect:
step1: step1:
choose: "We detected {{count}} connected devices, please select one:" choose: "We detected {{count}} connected devices, please select one:"
@ -151,7 +158,7 @@ addAccounts:
connectDevice: Device connectDevice: Device
import: Accounts import: Accounts
finish: Confirmation finish: Confirmation
accountAlreadyImportedSubtitle: '{{count}} Already in portfolio' accountAlreadyImportedSubtitle: 'Accounts already in portfolio ({{count}})'
accountToImportSubtitle: 'Add existing account' accountToImportSubtitle: 'Add existing account'
accountToImportSubtitle_plural: 'Add existing accounts' accountToImportSubtitle_plural: 'Add existing accounts'
selectAll: Select all ({{count}}) selectAll: Select all ({{count}})
@ -159,14 +166,16 @@ addAccounts:
editName: Edit name editName: Edit name
newAccount: New account newAccount: New account
legacyAccount: '{{accountName}} (legacy)' legacyAccount: '{{accountName}} (legacy)'
legacyUnsplitAccount: '{{accountName}} (legacy) (unsplit)'
unsplitAccount: '{{accountName}} (unsplit)'
noAccountToImport: No existing {{currencyName}} accounts to add noAccountToImport: No existing {{currencyName}} accounts to add
success: Account successfully added to your portfolio success: Account successfully added to your portfolio
success_plural: Accounts 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. successDescription_plural: Your accounts have been created.
createNewAccount: createNewAccount:
title: Add a new account 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 noAccountToCreate: No <1><0>{{currencyName}}</0></1> account was found to create
somethingWentWrong: Something went wrong during synchronization, please try again. somethingWentWrong: Something went wrong during synchronization, please try again.
cta: cta:
@ -217,12 +226,15 @@ manager:
idCheck: Identifier check idCheck: Identifier check
updateMCU: Update MCU updateMCU: Update MCU
confirm: Confirmation confirm: Confirmation
installing: Installing firmware
flashing: Installing MCU
confirmIdentifier: Confirm identifier confirmIdentifier: Confirm identifier
confirmIdentifierText: Please confirm identifier on your Device. Be sure the identifier is the same as below confirmIdentifierText: Please confirm identifier on your Device. Be sure the identifier is the same as below
identifier: Identifier identifier: Identifier
mcuTitle: Updating MCU mcuTitle: Updating MCU
mcuFirst: Unplug your device from your computer mcuFirst: Unplug your device from your computer
mcuSecond: Press and hold left button and plug your device until the processing screen appears 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 successTitle: Firmware has been updated with success
successText: You can now re-install your applications on your device successText: You can now re-install your applications on your device
title: Manager title: Manager
@ -246,8 +258,8 @@ receive:
withoutDevice: Don't have your device? withoutDevice: Don't have your device?
confirmAddress: confirmAddress:
title: Verification title: Verification
action: Confirm address on device action: Verify address on device
text: Verify that the address below matches the address displayed on your 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 support: Contact us
receiveFunds: receiveFunds:
title: Receive title: Receive
@ -298,7 +310,8 @@ settings:
display: General display: General
currencies: Currencies currencies: Currencies
profile: Profile profile: Profile
about: Help help: Help
about: About
display: display:
desc: Change settings that affect Ledger Live in general. desc: Change settings that affect Ledger Live in general.
language: Display language language: Display language
@ -343,6 +356,8 @@ settings:
reportErrors: Report bugs reportErrors: Report bugs
reportErrorsDesc: Share anonymous usage and diagnostics data to help improve Ledger products, services and security features. reportErrorsDesc: Share anonymous usage and diagnostics data to help improve Ledger products, services and security features.
about: about:
desc: Learn about Ledger Live features
help:
desc: Learn about Ledger Live features or get help. desc: Learn about Ledger Live features or get help.
version: Version version: Version
releaseNotesBtn: Details releaseNotesBtn: Details
@ -408,5 +423,5 @@ crash:
disclaimerModal: disclaimerModal:
title: Trade safely 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_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 cta: Got it

8
static/i18n/fr/errors.yml

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

26
static/i18n/fr/onboarding.yml

@ -130,11 +130,33 @@ analytics:
title: Analytics and bug reports title: Analytics and bug reports
desc: Share anonymous usage and diagnostics data to help improve Ledger products, services and security features. desc: Share anonymous usage and diagnostics data to help improve Ledger products, services and security features.
shareAnalytics: shareAnalytics:
title: Share usage data title: Share analytics
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. 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: sentryLogs:
title: Report bugs title: Report bugs
desc: Automatically send reports to help Ledger fix 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: finish:
title: Your device is ready! title: Your device is ready!
desc: Proceed to your portfolio and start adding your accounts... desc: Proceed to your portfolio and start adding your accounts...

Loading…
Cancel
Save