Browse Source

Merge branch 'develop' into device-interaction

master
meriadec 7 years ago
parent
commit
23ad9668ab
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 10
      scripts/download-analytics.sh
  2. 29
      src/analytics/Track.js
  3. 14
      src/analytics/TrackPage.js
  4. 18
      src/analytics/inject-in-window.js
  5. 100
      src/analytics/segment.js
  6. 8
      src/components/AccountPage/index.js
  7. 8
      src/components/DashboardPage/index.js
  8. 29
      src/components/ExchangePage/ExchangeCard.js
  9. 18
      src/components/ExchangePage/index.js
  10. 14
      src/components/OperationsList/index.js
  11. 2
      src/components/SettingsPage/sections/About.js
  12. 2
      src/components/SettingsPage/sections/Currencies.js
  13. 20
      src/components/SettingsPage/sections/Display.js
  14. 2
      src/components/SettingsPage/sections/Profile.js
  15. 2
      src/components/modals/AddAccounts/index.js
  16. 2
      src/components/modals/AddAccounts/steps/01-step-choose-currency.js
  17. 4
      src/components/modals/AddAccounts/steps/02-step-connect-device.js
  18. 2
      src/components/modals/AddAccounts/steps/03-step-import.js
  19. 2
      src/components/modals/AddAccounts/steps/04-step-finish.js
  20. 25
      src/components/modals/Receive/01-step-account.js
  21. 16
      src/components/modals/Receive/02-step-connect-device.js
  22. 59
      src/components/modals/Receive/03-step-confirm-address.js
  23. 48
      src/components/modals/Receive/04-step-receive-funds.js
  24. 6
      src/components/modals/Receive/index.js
  25. 2
      src/components/modals/Receive/steps/01-step-account.js
  26. 2
      src/components/modals/Receive/steps/03-step-confirm-address.js
  27. 2
      src/components/modals/Receive/steps/04-step-receive-funds.js
  28. 3
      src/components/modals/Send/01-step-amount.js
  29. 16
      src/components/modals/Send/02-step-connect-device.js
  30. 2
      src/components/modals/Send/03-step-verification.js
  31. 2
      src/components/modals/Send/04-step-confirmation.js
  32. 4
      src/components/modals/Send/index.js
  33. 1
      src/components/modals/StepConnectDevice.js
  34. 1
      src/config/constants.js
  35. 8
      src/helpers/staticPath.js
  36. 11
      src/helpers/systemLocale.js
  37. 18
      src/middlewares/analytics.js
  38. 47
      src/reducers/settings.js
  39. 1
      src/renderer/createStore.js
  40. 4
      src/renderer/init.js
  41. 11
      static/analytics.min.js
  42. 138
      static/i18n/en/app.yml
  43. 8
      static/i18n/en/errors.yml
  44. 1
      static/i18n/en/language.yml
  45. 16
      static/i18n/en/onboarding.yml
  46. 2
      static/i18n/fr/app.yml

10
scripts/download-analytics.sh

@ -0,0 +1,10 @@
#!/bin/bash
if [ -z $ANALYTICS_KEY ]; then
echo 'ANALYTICS_KEY must be set'
exit 1
fi
cd `dirname $0`/..
wget https://cdn.segment.com/analytics.js/v1/$ANALYTICS_KEY/analytics.min.js -O static/analytics.min.js

29
src/analytics/Track.js

@ -0,0 +1,29 @@
import { PureComponent } from 'react'
import { track } from './segment'
class Track extends PureComponent<{
onMount?: boolean,
onUnmount?: boolean,
onUpdate?: boolean,
event: string,
properties?: Object,
}> {
componentDidMount() {
if (this.props.onMount) this.track()
}
componentDidUpdate() {
if (this.props.onUpdate) this.track()
}
componentWillUnmount() {
if (this.props.onUnmount) this.track()
}
track = () => {
const { event, properties } = this.props
track(event, properties)
}
render() {
return null
}
}
export default Track

14
src/analytics/TrackPage.js

@ -0,0 +1,14 @@
import { PureComponent } from 'react'
import { page } from './segment'
class TrackPage extends PureComponent<{ category: string, name?: string, properties?: Object }> {
componentDidMount() {
const { category, name, properties } = this.props
page(category, name, properties)
}
render() {
return null
}
}
export default TrackPage

18
src/analytics/inject-in-window.js

@ -0,0 +1,18 @@
/* eslint-disable */
import { getPath } from 'helpers/staticPath'
// prettier-ignore
!function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t<analytics.methods.length;t++){var e=analytics.methods[t];analytics[e]=analytics.factory(e)};analytics.SNIPPET_VERSION="4.1.0";
}}();
let loaded = false
export const load = () => {
if (loaded) return
loaded = true
var n = document.createElement('script')
n.type = 'text/javascript'
n.async = !0
n.src = getPath('analytics.min.js')
var a = document.getElementsByTagName('script')[0]
a.parentNode.insertBefore(n, a)
}

100
src/analytics/segment.js

@ -0,0 +1,100 @@
// @flow
import uuid from 'uuid/v4'
import logger from 'logger'
import invariant from 'invariant'
import user from 'helpers/user'
import { DEBUG_ANALYTICS } from 'config/constants'
import { langAndRegionSelector } from 'reducers/settings'
import { getSystemLocale } from 'helpers/systemLocale'
import { load } from './inject-in-window'
invariant(typeof window !== 'undefined', 'analytics/segment must be called on renderer thread')
const sessionId = uuid()
const getContext = store => {
const state = store.getState()
const { language, region } = langAndRegionSelector(state)
const systemLocale = getSystemLocale()
return {
ip: '0.0.0.0',
appVersion: __APP_VERSION__,
language,
region,
environment: __DEV__ ? 'development' : 'production',
systemLanguage: systemLocale.language,
systemRegion: systemLocale.region,
sessionId,
}
}
let storeInstance // is the redux store. it's also used as a flag to know if analytics is on or off.
export const start = (store: *) => {
storeInstance = store
const { analytics } = window
if (typeof analytics === 'undefined') {
logger.error('analytics is not available')
return
}
const { id } = user()
load()
analytics.identify(
id,
{},
{
context: getContext(store),
},
)
if (DEBUG_ANALYTICS) {
logger.log(`analytics: start() with user id ${id}`)
}
}
export const stop = () => {
storeInstance = null
const { analytics } = window
if (typeof analytics === 'undefined') {
logger.error('analytics is not available')
return
}
analytics.reset()
if (DEBUG_ANALYTICS) {
logger.log(`analytics: stop()`)
}
}
export const track = (event: string, properties: ?Object) => {
if (!storeInstance) {
return
}
const { analytics } = window
if (typeof analytics === 'undefined') {
logger.error('analytics is not available')
return
}
analytics.track(event, properties, {
context: getContext(storeInstance),
})
if (DEBUG_ANALYTICS) {
logger.log(`analytics: track(${event},`, properties)
}
}
export const page = (category: string, name: ?string, properties: ?Object) => {
if (!storeInstance) {
return
}
const { analytics } = window
if (typeof analytics === 'undefined') {
logger.error('analytics is not available')
return
}
analytics.page(category, name, properties, {
context: getContext(storeInstance),
})
if (DEBUG_ANALYTICS) {
logger.log(`analytics: page(${category}, ${name || ''},`, properties)
}
}

8
src/components/AccountPage/index.js

@ -9,6 +9,7 @@ import styled from 'styled-components'
import type { Currency, Account } from '@ledgerhq/live-common/lib/types' import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
import SyncOneAccountOnMount from 'components/SyncOneAccountOnMount' import SyncOneAccountOnMount from 'components/SyncOneAccountOnMount'
import Tooltip from 'components/base/Tooltip' import Tooltip from 'components/base/Tooltip'
import TrackPage from 'analytics/TrackPage'
import { MODAL_SEND, MODAL_RECEIVE, MODAL_SETTINGS_ACCOUNT } from 'config/constants' import { MODAL_SEND, MODAL_RECEIVE, MODAL_SETTINGS_ACCOUNT } from 'config/constants'
@ -106,6 +107,13 @@ class AccountPage extends PureComponent<Props> {
return ( return (
// Force re-render account page, for avoid animation // Force re-render account page, for avoid animation
<Box key={account.id}> <Box key={account.id}>
<TrackPage
category="Account"
properties={{
currency: account.currency.id,
operationsLength: account.operations.length,
}}
/>
<SyncOneAccountOnMount priority={10} accountId={account.id} /> <SyncOneAccountOnMount priority={10} accountId={account.id} />
<Box horizontal mb={5} flow={4}> <Box horizontal mb={5} flow={4}>
<AccountHeader account={account} /> <AccountHeader account={account} />

8
src/components/DashboardPage/index.js

@ -1,6 +1,7 @@
// @flow // @flow
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import uniq from 'lodash/uniq'
import { compose } from 'redux' import { compose } from 'redux'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
@ -26,6 +27,7 @@ import type { TimeRange } from 'reducers/settings'
import { reorderAccounts } from 'actions/accounts' import { reorderAccounts } from 'actions/accounts'
import { saveSettings } from 'actions/settings' import { saveSettings } from 'actions/settings'
import TrackPage from 'analytics/TrackPage'
import UpdateNotifier from 'components/UpdateNotifier' import UpdateNotifier from 'components/UpdateNotifier'
import BalanceInfos from 'components/BalanceSummary/BalanceInfos' import BalanceInfos from 'components/BalanceSummary/BalanceInfos'
import BalanceSummary from 'components/BalanceSummary' import BalanceSummary from 'components/BalanceSummary'
@ -92,12 +94,18 @@ class DashboardPage extends PureComponent<Props> {
const timeFrame = this.handleGreeting() const timeFrame = this.handleGreeting()
const imagePath = i('empty-account-tile.svg') const imagePath = i('empty-account-tile.svg')
const totalAccounts = accounts.length const totalAccounts = accounts.length
const totalCurrencies = uniq(accounts.map(a => a.currency.id)).length
const totalOperations = accounts.reduce((sum, a) => sum + a.operations.length, 0)
const displayOperationsHelper = (account: Account) => account.operations.length > 0 const displayOperationsHelper = (account: Account) => account.operations.length > 0
const displayOperations = accounts.some(displayOperationsHelper) const displayOperations = accounts.some(displayOperationsHelper)
return ( return (
<Fragment> <Fragment>
<UpdateNotifier /> <UpdateNotifier />
<TrackPage
category="Portfolio"
properties={{ totalAccounts, totalOperations, totalCurrencies }}
/>
<Box flow={7}> <Box flow={7}>
{totalAccounts > 0 ? ( {totalAccounts > 0 ? (
<Fragment> <Fragment>

29
src/components/ExchangePage/ExchangeCard.js

@ -1,7 +1,8 @@
// @flow // @flow
import React from 'react' import React, { PureComponent } from 'react'
import { shell } from 'electron' import { shell } from 'electron'
import { track } from 'analytics/segment'
import type { T } from 'types/common' import type { T } from 'types/common'
@ -10,26 +11,29 @@ import Box, { Card } from 'components/base/Box'
import { FakeLink } from 'components/base/Link' import { FakeLink } from 'components/base/Link'
type CardType = { type CardType = {
id: string,
logo: any, logo: any,
desc: string,
url: string, url: string,
} }
export default function ExchangeCard({ t, card }: { t: T, card: CardType }) { export default class ExchangeCard extends PureComponent<{ t: T, card: CardType }> {
const { logo, desc } = card onClick = () => {
const { card } = this.props
shell.openExternal(card.url)
track('VisitExchange', { id: card.id, url: card.url })
}
render() {
const {
card: { logo, id },
t,
} = this.props
return ( return (
<Card <Card horizontal py={5} px={6} style={{ cursor: 'pointer' }} onClick={this.onClick}>
horizontal
py={5}
px={6}
style={{ cursor: 'pointer' }}
onClick={() => shell.openExternal(card.url)}
>
<Box justify="center" style={{ width: 200 }}> <Box justify="center" style={{ width: 200 }}>
{logo} {logo}
</Box> </Box>
<Box shrink ff="Open Sans|Regular" fontSize={4} flow={3}> <Box shrink ff="Open Sans|Regular" fontSize={4} flow={3}>
<Box>{desc}</Box> <Box>{t(`app:exchange.${id}`)}</Box>
<Box horizontal align="center" color="wallet" flow={1}> <Box horizontal align="center" color="wallet" flow={1}>
<FakeLink>{t('app:exchange.visitWebsite')}</FakeLink> <FakeLink>{t('app:exchange.visitWebsite')}</FakeLink>
<ExternalLinkIcon size={14} /> <ExternalLinkIcon size={14} />
@ -37,4 +41,5 @@ export default function ExchangeCard({ t, card }: { t: T, card: CardType }) {
</Box> </Box>
</Card> </Card>
) )
}
} }

18
src/components/ExchangePage/index.js

@ -5,6 +5,7 @@ import { translate } from 'react-i18next'
import type { T } from 'types/common' import type { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import ExchangeCard from './ExchangeCard' import ExchangeCard from './ExchangeCard'
@ -16,32 +17,33 @@ type Props = {
t: T, t: T,
} }
class ExchangePage extends PureComponent<Props> { const cards = [
render() {
const { t } = this.props
const cards = [
{ {
key: 'coinhouse', key: 'coinhouse',
id: 'coinhouse',
url: 'https://www.coinhouse.com/r/157530', url: 'https://www.coinhouse.com/r/157530',
logo: <CoinhouseLogo width={150} />, logo: <CoinhouseLogo width={150} />,
desc: t('app:exchange.coinhouse'),
}, },
{ {
key: 'changelly', key: 'changelly',
id: 'coinhouse',
url: 'https://changelly.com/?ref_id=aac789605a01', url: 'https://changelly.com/?ref_id=aac789605a01',
logo: <ChangellyLogo width={150} />, logo: <ChangellyLogo width={150} />,
desc: t('app:exchange.changelly'),
}, },
{ {
key: 'coinmama', key: 'coinmama',
id: 'coinhouse',
url: 'http://go.coinmama.com/visit/?bta=51801&nci=5343', url: 'http://go.coinmama.com/visit/?bta=51801&nci=5343',
logo: <CoinmamaLogo width={150} />, logo: <CoinmamaLogo width={150} />,
desc: t('app:exchange.coinmama'),
}, },
] ]
class ExchangePage extends PureComponent<Props> {
render() {
const { t } = this.props
return ( return (
<Box pb={6}> <Box pb={6}>
<TrackPage category="Exchange" />
<Box ff="Museo Sans|Regular" color="dark" fontSize={7} mb={5}> <Box ff="Museo Sans|Regular" color="dark" fontSize={7} mb={5}>
{t('app:exchange.title')} {t('app:exchange.title')}
</Box> </Box>

14
src/components/OperationsList/index.js

@ -25,6 +25,7 @@ import IconAngleDown from 'icons/AngleDown'
import Box, { Card } from 'components/base/Box' import Box, { Card } from 'components/base/Box'
import Text from 'components/base/Text' import Text from 'components/base/Text'
import Defer from 'components/base/Defer' import Defer from 'components/base/Defer'
import Track from 'analytics/Track'
import SectionTitle from './SectionTitle' import SectionTitle from './SectionTitle'
import OperationC from './Operation' import OperationC from './Operation'
@ -129,6 +130,19 @@ export class OperationsList extends PureComponent<Props, State> {
</Card> </Card>
</Box> </Box>
))} ))}
{groupedOperations.completed ? (
<Track
onMount
event="OperationsListEndReached"
properties={{
totalSections: groupedOperations.sections.length,
totalOperations: groupedOperations.sections.reduce(
(sum, s) => sum + s.data.length,
0,
),
}}
/>
) : null}
{!groupedOperations.completed ? ( {!groupedOperations.completed ? (
<ShowMore onClick={this.fetchMoreOperations}> <ShowMore onClick={this.fetchMoreOperations}>
<span>{t('app:common.showMore')}</span> <span>{t('app:common.showMore')}</span>

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

@ -14,6 +14,7 @@ import { Tabbable } from 'components/base/Box'
import { openModal } from 'reducers/modals' import { openModal } from 'reducers/modals'
import { MODAL_RELEASES_NOTES } from 'config/constants' import { MODAL_RELEASES_NOTES } from 'config/constants'
import TrackPage from 'analytics/TrackPage'
import { import {
SettingsSection as Section, SettingsSection as Section,
@ -61,6 +62,7 @@ class SectionAbout extends PureComponent<Props> {
return ( return (
<Section> <Section>
<TrackPage category="Settings" name="About" />
<Header <Header
icon={<IconHelp size={16} />} icon={<IconHelp size={16} />}
title={t('app:settings.tabs.about')} title={t('app:settings.tabs.about')}

2
src/components/SettingsPage/sections/Currencies.js

@ -18,6 +18,7 @@ import type { SettingsState } from 'reducers/settings'
import { currenciesSelector } from 'reducers/accounts' import { currenciesSelector } from 'reducers/accounts'
import { currencySettingsDefaults } from 'helpers/SettingsDefaults' import { currencySettingsDefaults } from 'helpers/SettingsDefaults'
import TrackPage from 'analytics/TrackPage'
import SelectCurrency from 'components/SelectCurrency' import SelectCurrency from 'components/SelectCurrency'
import StepperNumber from 'components/base/StepperNumber' import StepperNumber from 'components/base/StepperNumber'
import ExchangeSelect from 'components/SelectExchange' import ExchangeSelect from 'components/SelectExchange'
@ -94,6 +95,7 @@ class TabCurrencies extends PureComponent<Props, State> {
const defaults = currencySettingsDefaults(currency) const defaults = currencySettingsDefaults(currency)
return ( return (
<Section key={currency.id}> <Section key={currency.id}>
<TrackPage category="Settings" name="Currencies" />
<Header <Header
icon={<IconCurrencies size={16} />} icon={<IconCurrencies size={16} />}
title={t('app:settings.tabs.currencies')} title={t('app:settings.tabs.currencies')}

20
src/components/SettingsPage/sections/Display.js

@ -13,6 +13,7 @@ import {
import type { SettingsState as Settings } from 'reducers/settings' import type { SettingsState as Settings } from 'reducers/settings'
import type { T } from 'types/common' import type { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import SelectExchange from 'components/SelectExchange' import SelectExchange from 'components/SelectExchange'
import Select from 'components/base/Select' import Select from 'components/base/Select'
@ -52,9 +53,9 @@ type Props = {
type State = { type State = {
cachedMarketIndicator: string, cachedMarketIndicator: string,
cachedLanguageKey: string, cachedLanguageKey: ?string,
cachedCounterValue: ?Object, cachedCounterValue: ?Object,
cachedRegion: string, cachedRegion: ?string,
} }
class TabProfile extends PureComponent<Props, State> { class TabProfile extends PureComponent<Props, State> {
@ -131,9 +132,12 @@ class TabProfile extends PureComponent<Props, State> {
const counterValueCurrency = counterValueCurrencyLocalSelector(settings) const counterValueCurrency = counterValueCurrencyLocalSelector(settings)
const counterValueExchange = counterValueExchangeLocalSelector(settings) const counterValueExchange = counterValueExchangeLocalSelector(settings)
const languages = languageKeys.map(key => ({ value: key, label: t(`language:${key}`) })) const languages = [{ value: null, label: t(`language:system`) }].concat(
const currentLanguage = languages.find(l => l.value === cachedLanguageKey) languageKeys.map(key => ({ value: key, label: t(`language:${key}`) })),
)
const regionsFiltered = regions.filter(({ language }) => cachedLanguageKey === language) const regionsFiltered = regions.filter(({ language }) => cachedLanguageKey === language)
const currentLanguage = languages.find(l => l.value === cachedLanguageKey)
const currentRegion = const currentRegion =
regionsFiltered.find(({ region }) => cachedRegion === region) || regionsFiltered[0] regionsFiltered.find(({ region }) => cachedRegion === region) || regionsFiltered[0]
@ -143,6 +147,7 @@ class TabProfile extends PureComponent<Props, State> {
return ( return (
<Section> <Section>
<TrackPage category="Settings" name="Display" />
<Header <Header
icon={<IconDisplay size={16} />} icon={<IconDisplay size={16} />}
title={t('app:settings.tabs.display')} title={t('app:settings.tabs.display')}
@ -187,7 +192,11 @@ class TabProfile extends PureComponent<Props, State> {
options={languages} options={languages}
/> />
</Row> </Row>
<Row title={t('app:settings.display.region')} desc={t('app:settings.display.regionDesc')}> {regionsFiltered.length === 0 ? null : (
<Row
title={t('app:settings.display.region')}
desc={t('app:settings.display.regionDesc')}
>
<Select <Select
small small
minWidth={250} minWidth={250}
@ -197,6 +206,7 @@ class TabProfile extends PureComponent<Props, State> {
options={regionsFiltered} options={regionsFiltered}
/> />
</Row> </Row>
)}
<Row title={t('app:settings.display.stock')} desc={t('app:settings.display.stockDesc')}> <Row title={t('app:settings.display.stock')} desc={t('app:settings.display.stockDesc')}>
<RadioGroup <RadioGroup
items={this.getMarketIndicators()} items={this.getMarketIndicators()}

2
src/components/SettingsPage/sections/Profile.js

@ -15,6 +15,7 @@ import hardReset from 'helpers/hardReset'
import type { SettingsState } from 'reducers/settings' import type { SettingsState } from 'reducers/settings'
import type { T } from 'types/common' import type { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import ExportLogsBtn from 'components/ExportLogsBtn' import ExportLogsBtn from 'components/ExportLogsBtn'
import CheckBox from 'components/base/CheckBox' import CheckBox from 'components/base/CheckBox'
import Box from 'components/base/Box' import Box from 'components/base/Box'
@ -145,6 +146,7 @@ class TabProfile extends PureComponent<Props, State> {
const isPasswordEnabled = settings.password.isEnabled === true const isPasswordEnabled = settings.password.isEnabled === true
return ( return (
<Section> <Section>
<TrackPage category="Settings" name="Profile" />
<Header <Header
icon={<IconUser size={16} />} icon={<IconUser size={16} />}
title={t('app:settings.tabs.profile')} title={t('app:settings.tabs.profile')}

2
src/components/modals/AddAccounts/index.js

@ -6,6 +6,7 @@ import { connect } from 'react-redux'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { createStructuredSelector } from 'reselect' import { createStructuredSelector } from 'reselect'
import Track from 'analytics/Track'
import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority' import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority'
import type { Currency, Account } from '@ledgerhq/live-common/lib/types' import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
@ -227,6 +228,7 @@ class AddAccounts extends PureComponent<Props, State> {
steps={this.STEPS} steps={this.STEPS}
{...addtionnalProps} {...addtionnalProps}
> >
<Track onUnmount event="CloseModalAddAccounts" />
<SyncSkipUnderPriority priority={100} /> <SyncSkipUnderPriority priority={100} />
</Stepper> </Stepper>
)} )}

2
src/components/modals/AddAccounts/steps/01-step-choose-currency.js

@ -2,6 +2,7 @@
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import TrackPage from 'analytics/TrackPage'
import SelectCurrency from 'components/SelectCurrency' import SelectCurrency from 'components/SelectCurrency'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import CurrencyBadge from 'components/base/CurrencyBadge' import CurrencyBadge from 'components/base/CurrencyBadge'
@ -15,6 +16,7 @@ function StepChooseCurrency({ currency, setCurrency }: StepProps) {
export function StepChooseCurrencyFooter({ transitionTo, currency, t }: StepProps) { export function StepChooseCurrencyFooter({ transitionTo, currency, t }: StepProps) {
return ( return (
<Fragment> <Fragment>
<TrackPage category="AddAccounts" name="Step1" />
{currency && <CurrencyBadge mr="auto" currency={currency} />} {currency && <CurrencyBadge mr="auto" currency={currency} />}
<Button primary disabled={!currency} onClick={() => transitionTo('connectDevice')}> <Button primary disabled={!currency} onClick={() => transitionTo('connectDevice')}>
{t('app:common.next')} {t('app:common.next')}

4
src/components/modals/AddAccounts/steps/02-step-connect-device.js

@ -4,6 +4,7 @@ import invariant from 'invariant'
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import { Trans } from 'react-i18next' import { Trans } from 'react-i18next'
import TrackPage from 'analytics/TrackPage'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import ConnectDevice from 'components/modals/StepConnectDevice' import ConnectDevice from 'components/modals/StepConnectDevice'
@ -12,10 +13,11 @@ import { CurrencyCircleIcon } from 'components/base/CurrencyBadge'
import type { StepProps } from '../index' import type { StepProps } from '../index'
function StepConnectDevice({ t, currency, device, setAppOpened }: StepProps) { function StepConnectDevice({ t, currency, device, setAppOpened }: StepProps) {
invariant(currency, 'No currency given') invariant(currency, 'No crypto asset given')
return ( return (
<Fragment> <Fragment>
<TrackPage category="AddAccounts" name="Step2" />
<Box align="center" mb={6}> <Box align="center" mb={6}>
<CurrencyCircleIcon mb={3} size={40} currency={currency} /> <CurrencyCircleIcon mb={3} size={40} currency={currency} />
<Box ff="Open Sans" fontSize={4} color="dark" textAlign="center" style={{ width: 370 }}> <Box ff="Open Sans" fontSize={4} color="dark" textAlign="center" style={{ width: 370 }}>

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

@ -7,6 +7,7 @@ import uniq from 'lodash/uniq'
import { getBridgeForCurrency } from 'bridge' import { getBridgeForCurrency } from 'bridge'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import CurrencyBadge from 'components/base/CurrencyBadge' import CurrencyBadge from 'components/base/CurrencyBadge'
import Button from 'components/base/Button' import Button from 'components/base/Button'
@ -199,6 +200,7 @@ class StepImport extends PureComponent<StepProps> {
return ( return (
<Fragment> <Fragment>
<TrackPage category="AddAccounts" name="Step3" />
<Box flow={5}> <Box flow={5}>
<AccountsList <AccountsList
title={importableAccountsListTitle} title={importableAccountsListTitle}

2
src/components/modals/AddAccounts/steps/04-step-finish.js

@ -2,6 +2,7 @@
import React from 'react' import React from 'react'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import IconCheckCircle from 'icons/CheckCircle' import IconCheckCircle from 'icons/CheckCircle'
@ -11,6 +12,7 @@ import type { StepProps } from '../index'
function StepFinish({ onCloseModal, t }: StepProps) { function StepFinish({ onCloseModal, t }: StepProps) {
return ( return (
<Box align="center" py={6}> <Box align="center" py={6}>
<TrackPage category="AddAccounts" name="Step4" />
<Box color="positiveGreen" mb={4}> <Box color="positiveGreen" mb={4}>
<IconCheckCircle size={40} /> <IconCheckCircle size={40} />
</Box> </Box>

25
src/components/modals/Receive/01-step-account.js

@ -0,0 +1,25 @@
// @flow
import React from 'react'
import type { Account } from '@ledgerhq/live-common/lib/types'
import type { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box'
import Label from 'components/base/Label'
import SelectAccount from 'components/SelectAccount'
type Props = {
account: ?Account,
onChangeAccount: Function,
t: T,
}
export default (props: Props) => (
<Box flow={1}>
<TrackPage category="Receive" name="Step1" />
<Label>{props.t('app:receive.steps.chooseAccount.label')}</Label>
<SelectAccount autoFocus onChange={props.onChangeAccount} value={props.account} />
</Box>
)

16
src/components/modals/Receive/02-step-connect-device.js

@ -0,0 +1,16 @@
import React, { Component, Fragment } from 'react'
import TrackPage from 'analytics/TrackPage'
import StepConnectDevice from '../StepConnectDevice'
class ReceiveStepConnectDevice extends Component<*> {
render() {
return (
<Fragment>
<TrackPage category="Receive" name="Step2" />
<StepConnectDevice {...this.props} />
</Fragment>
)
}
}
export default ReceiveStepConnectDevice

59
src/components/modals/Receive/03-step-confirm-address.js

@ -0,0 +1,59 @@
// @flow
import React, { Fragment } from 'react'
import styled from 'styled-components'
import type { Account } from '@ledgerhq/live-common/lib/types'
import type { Device, T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box'
import CurrentAddressForAccount from 'components/CurrentAddressForAccount'
import DeviceConfirm from 'components/DeviceConfirm'
const Container = styled(Box).attrs({
alignItems: 'center',
fontSize: 4,
color: 'dark',
px: 7,
})``
const Title = styled(Box).attrs({
ff: 'Museo Sans|Regular',
fontSize: 6,
mb: 1,
})``
const Text = styled(Box).attrs({
color: 'smoke',
})`
text-align: center;
`
type Props = {
account: ?Account,
addressVerified: ?boolean,
device: ?Device,
t: T,
}
export default (props: Props) => (
<Container>
<TrackPage category="Receive" name="Step3" />
{props.addressVerified === false ? (
<Fragment>
<Title>{props.t('app:receive.steps.confirmAddress.error.title')}</Title>
<Text mb={5}>{props.t('app:receive.steps.confirmAddress.error.text')}</Text>
<DeviceConfirm error />
</Fragment>
) : (
<Fragment>
<Title>{props.t('app:receive.steps.confirmAddress.action')}</Title>
<Text>{props.t('app:receive.steps.confirmAddress.text')}</Text>
{props.account && <CurrentAddressForAccount account={props.account} />}
{props.device &&
props.account && <DeviceConfirm mb={2} mt={-1} error={props.addressVerified === false} />}
</Fragment>
)}
</Container>
)

48
src/components/modals/Receive/04-step-receive-funds.js

@ -0,0 +1,48 @@
// @flow
import React from 'react'
import type { Account } from '@ledgerhq/live-common/lib/types'
import type { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box'
import CurrentAddressForAccount from 'components/CurrentAddressForAccount'
import Label from 'components/base/Label'
import RequestAmount from 'components/RequestAmount'
type Props = {
account: ?Account,
addressVerified: ?boolean,
amount: string | number,
onChangeAmount: Function,
onVerify: Function,
t: T,
}
export default (props: Props) => (
<Box flow={5}>
<TrackPage category="Receive" name="Step4" />
<Box flow={1}>
<Label>{props.t('app:receive.steps.receiveFunds.label')}</Label>
<RequestAmount
account={props.account}
onChange={props.onChangeAmount}
value={props.amount}
withMax={false}
/>
</Box>
{props.account && (
<CurrentAddressForAccount
account={props.account}
addressVerified={props.addressVerified}
amount={props.amount}
onVerify={props.onVerify}
withBadge
withFooter
withQRCode
withVerify={props.addressVerified === false}
/>
)}
</Box>
)

6
src/components/modals/Receive/index.js

@ -1,6 +1,6 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { PureComponent, Fragment } from 'react'
import { compose } from 'redux' import { compose } from 'redux'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
@ -8,6 +8,7 @@ import { createStructuredSelector } from 'reselect'
import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority' import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority'
import Track from 'analytics/Track'
import type { Account } from '@ledgerhq/live-common/lib/types' import type { Account } from '@ledgerhq/live-common/lib/types'
import { MODAL_RECEIVE } from 'config/constants' import { MODAL_RECEIVE } from 'config/constants'
@ -182,6 +183,8 @@ class ReceiveModal extends PureComponent<Props, State> {
preventBackdropClick={isModalLocked} preventBackdropClick={isModalLocked}
onBeforeOpen={this.handleBeforeOpenModal} onBeforeOpen={this.handleBeforeOpenModal}
render={({ onClose }) => ( render={({ onClose }) => (
<Fragment>
<Track onUnmount event="CloseModalReceive" />
<Stepper <Stepper
title={t('app:receive.title')} title={t('app:receive.title')}
initialStepId={stepId} initialStepId={stepId}
@ -194,6 +197,7 @@ class ReceiveModal extends PureComponent<Props, State> {
> >
<SyncSkipUnderPriority priority={100} /> <SyncSkipUnderPriority priority={100} />
</Stepper> </Stepper>
</Fragment>
)} )}
/> />
) )

2
src/components/modals/Receive/steps/01-step-account.js

@ -2,6 +2,7 @@
import React from 'react' import React from 'react'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Label from 'components/base/Label' import Label from 'components/base/Label'
import Button from 'components/base/Button' import Button from 'components/base/Button'
@ -12,6 +13,7 @@ import type { StepProps } from '../index'
export default function StepAccount({ t, account, onChangeAccount }: StepProps) { export default function StepAccount({ t, account, onChangeAccount }: StepProps) {
return ( return (
<Box flow={1}> <Box flow={1}>
<TrackPage category="Receive" name="Step1" />
<Label>{t('app:receive.steps.chooseAccount.label')}</Label> <Label>{t('app:receive.steps.chooseAccount.label')}</Label>
<SelectAccount autoFocus onChange={onChangeAccount} value={account} /> <SelectAccount autoFocus onChange={onChangeAccount} value={account} />
</Box> </Box>

2
src/components/modals/Receive/steps/03-step-confirm-address.js

@ -7,6 +7,7 @@ import React, { Fragment, PureComponent } from 'react'
import getAddress from 'commands/getAddress' import getAddress from 'commands/getAddress'
import { isSegwitAccount } from 'helpers/bip32' import { isSegwitAccount } from 'helpers/bip32'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import DeviceConfirm from 'components/DeviceConfirm' import DeviceConfirm from 'components/DeviceConfirm'
@ -52,6 +53,7 @@ export default class StepConfirmAddress extends PureComponent<StepProps> {
invariant(device, 'No device given') invariant(device, 'No device given')
return ( return (
<Container> <Container>
<TrackPage category="Receive" name="Step3" />
{isAddressVerified === false ? ( {isAddressVerified === false ? (
<Fragment> <Fragment>
<Title>{t('app:receive.steps.confirmAddress.error.title')}</Title> <Title>{t('app:receive.steps.confirmAddress.error.title')}</Title>

2
src/components/modals/Receive/steps/04-step-receive-funds.js

@ -3,6 +3,7 @@
import invariant from 'invariant' import invariant from 'invariant'
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import TrackPage from 'analytics/TrackPage'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Label from 'components/base/Label' import Label from 'components/base/Label'
@ -33,6 +34,7 @@ export default class StepReceiveFunds extends PureComponent<StepProps, State> {
invariant(account, 'No account given') invariant(account, 'No account given')
return ( return (
<Box flow={5}> <Box flow={5}>
<TrackPage category="Receive" name="Step4" />
<Box flow={1}> <Box flow={1}>
<Label>{t('app:receive.steps.receiveFunds.label')}</Label> <Label>{t('app:receive.steps.receiveFunds.label')}</Label>
<RequestAmount <RequestAmount

3
src/components/modals/Send/01-step-amount.js

@ -3,6 +3,7 @@ import React, { Fragment } from 'react'
import type { Account } from '@ledgerhq/live-common/lib/types' import type { Account } from '@ledgerhq/live-common/lib/types'
import type { T } from 'types/common' import type { T } from 'types/common'
import type { WalletBridge } from 'bridge/types' import type { WalletBridge } from 'bridge/types'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import AccountField from './AccountField' import AccountField from './AccountField'
import RecipientField from './RecipientField' import RecipientField from './RecipientField'
@ -31,6 +32,8 @@ function StepAmount({
return ( return (
<Box flow={4}> <Box flow={4}>
<TrackPage category="Send" name="Step1" />
<AccountField t={t} onChange={onChangeAccount} value={account} /> <AccountField t={t} onChange={onChangeAccount} value={account} />
{/* HACK HACK HACK WTF */} {/* HACK HACK HACK WTF */}

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

@ -0,0 +1,16 @@
import React, { Component, Fragment } from 'react'
import TrackPage from 'analytics/TrackPage'
import StepConnectDevice from '../StepConnectDevice'
class SendStepConnectDevice extends Component<*> {
render() {
return (
<Fragment>
<TrackPage category="Send" name="Step2" />
<StepConnectDevice {...this.props} />
</Fragment>
)
}
}
export default SendStepConnectDevice

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

@ -3,6 +3,7 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import WarnBox from 'components/WarnBox' import WarnBox from 'components/WarnBox'
import { multiline } from 'styles/helpers' import { multiline } from 'styles/helpers'
@ -32,6 +33,7 @@ type Props = {
export default ({ t }: Props) => ( export default ({ t }: Props) => (
<Container> <Container>
<TrackPage category="Send" name="Step3" />
<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 />

2
src/components/modals/Send/04-step-confirmation.js

@ -4,6 +4,7 @@ import styled from 'styled-components'
import type { Operation } from '@ledgerhq/live-common/lib/types' import type { Operation } from '@ledgerhq/live-common/lib/types'
import type { T } from 'types/common' import type { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import Spinner from 'components/base/Spinner' import Spinner from 'components/base/Spinner'
import IconCheckCircle from 'icons/CheckCircle' import IconCheckCircle from 'icons/CheckCircle'
import IconExclamationCircleThin from 'icons/ExclamationCircleThin' import IconExclamationCircleThin from 'icons/ExclamationCircleThin'
@ -59,6 +60,7 @@ function StepConfirmation(props: Props) {
return ( return (
<Container> <Container>
<TrackPage category="Send" name="Step4" />
<span style={{ color: iconColor }}> <span style={{ color: iconColor }}>
<Icon size={43} /> <Icon size={43} />
</span> </span>

4
src/components/modals/Send/index.js

@ -8,6 +8,7 @@ import { connect } from 'react-redux'
import { compose } from 'redux' import { compose } from 'redux'
import { createStructuredSelector } from 'reselect' import { createStructuredSelector } from 'reselect'
import Track from 'analytics/Track'
import type { Account, Operation } from '@ledgerhq/live-common/lib/types' import type { Account, Operation } from '@ledgerhq/live-common/lib/types'
import type { T, Device } from 'types/common' import type { T, Device } from 'types/common'
import type { WalletBridge } from 'bridge/types' import type { WalletBridge } from 'bridge/types'
@ -21,7 +22,6 @@ import { MODAL_SEND } from 'config/constants'
import Modal, { ModalBody, ModalContent, ModalTitle } from 'components/base/Modal' import Modal, { ModalBody, ModalContent, ModalTitle } from 'components/base/Modal'
import PollCounterValuesOnMount from 'components/PollCounterValuesOnMount' import PollCounterValuesOnMount from 'components/PollCounterValuesOnMount'
import Breadcrumb from 'components/Breadcrumb' import Breadcrumb from 'components/Breadcrumb'
import StepConnectDevice from 'components/modals/StepConnectDevice'
import ChildSwitch from 'components/base/ChildSwitch' import ChildSwitch from 'components/base/ChildSwitch'
import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority' import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority'
import SyncOneAccountOnMount from 'components/SyncOneAccountOnMount' import SyncOneAccountOnMount from 'components/SyncOneAccountOnMount'
@ -30,6 +30,7 @@ import Footer from './Footer'
import ConfirmationFooter from './ConfirmationFooter' import ConfirmationFooter from './ConfirmationFooter'
import StepAmount from './01-step-amount' import StepAmount from './01-step-amount'
import StepConnectDevice from './02-step-connect-device'
import StepVerification from './03-step-verification' import StepVerification from './03-step-verification'
import StepConfirmation from './04-step-confirmation' import StepConfirmation from './04-step-confirmation'
@ -293,6 +294,7 @@ class SendModal extends Component<Props, State<*>> {
onClose={canClose ? this.handleReset : undefined} onClose={canClose ? this.handleReset : undefined}
render={({ onClose }) => ( render={({ onClose }) => (
<ModalBody onClose={canClose ? onClose : undefined}> <ModalBody onClose={canClose ? onClose : undefined}>
<Track onUnmount event="CloseModalSend" />
<PollCounterValuesOnMount /> <PollCounterValuesOnMount />
<SyncSkipUnderPriority priority={80} /> <SyncSkipUnderPriority priority={80} />
{account && <SyncOneAccountOnMount priority={81} accountId={account.id} />} {account && <SyncOneAccountOnMount priority={81} accountId={account.id} />}

1
src/components/modals/StepConnectDevice.js

@ -14,6 +14,7 @@ type Props = {
onStatusChange: (string, string) => void, onStatusChange: (string, string) => void,
} }
// FIXME why is that in modal !?
const StepConnectDevice = ({ account, currency, onChangeDevice, onStatusChange }: Props) => const StepConnectDevice = ({ account, currency, onChangeDevice, onStatusChange }: Props) =>
account || currency ? ( account || currency ? (
<EnsureDeviceApp <EnsureDeviceApp

1
src/config/constants.js

@ -59,6 +59,7 @@ export const BASE_SOCKET_URL_SECURE = stringFromEnv(
// Flags // Flags
export const DEBUG_ANALYTICS = boolFromEnv('DEBUG_ANALYTICS')
export const DEBUG_DEVICE = boolFromEnv('DEBUG_DEVICE') export const DEBUG_DEVICE = boolFromEnv('DEBUG_DEVICE')
export const DEBUG_NETWORK = boolFromEnv('DEBUG_NETWORK') export const DEBUG_NETWORK = boolFromEnv('DEBUG_NETWORK')
export const DEBUG_COMMANDS = boolFromEnv('DEBUG_COMMANDS') export const DEBUG_COMMANDS = boolFromEnv('DEBUG_COMMANDS')

8
src/helpers/staticPath.js

@ -14,13 +14,15 @@ const staticPath =
? `${__dirname}/../../static` ? `${__dirname}/../../static`
: 'static' : 'static'
export function getPath(path: string): string {
return isRunningInAsar ? `${staticPath}/${path}` : `/${path}`
}
/** /**
* Returns resolved static path for given image path * Returns resolved static path for given image path
* *
* note: `i` for `image` (using `img` was confusing when using with <img /> tag) * note: `i` for `image` (using `img` was confusing when using with <img /> tag)
*/ */
export function i(path: string): string { export const i = (path: string): string => getPath(`images/${path}`)
return isRunningInAsar ? `${staticPath}/images/${path}` : `/images/${path}`
}
export default staticPath export default staticPath

11
src/helpers/systemLocale.js

@ -0,0 +1,11 @@
// @flow
import memoize from 'lodash/memoize'
const parse = memoize(navLang => {
const localeSplit = (navLang || '').split('-')
const language = (localeSplit[0] || '').toLowerCase() || null
const region = (localeSplit[1] || '').toUpperCase() || null
return { language, region }
})
export const getSystemLocale = () => parse(window.navigator.language)

18
src/middlewares/analytics.js

@ -0,0 +1,18 @@
import { shareAnalyticsSelector } from 'reducers/settings'
import { start, stop } from 'analytics/segment'
let isAnalyticsStarted = false
export default store => next => action => {
next(action)
const state = store.getState()
const shareAnalytics = shareAnalyticsSelector(state)
if (shareAnalytics !== isAnalyticsStarted) {
isAnalyticsStarted = shareAnalytics
if (shareAnalytics) {
start(store)
} else {
stop()
}
}
}

47
src/reducers/settings.js

@ -12,6 +12,7 @@ import { createSelector } from 'reselect'
import type { InputSelector as Selector } from 'reselect' import type { InputSelector as Selector } from 'reselect'
import type { CryptoCurrency, Currency, Account } from '@ledgerhq/live-common/lib/types' import type { CryptoCurrency, Currency, Account } from '@ledgerhq/live-common/lib/types'
import { currencySettingsDefaults } from 'helpers/SettingsDefaults' import { currencySettingsDefaults } from 'helpers/SettingsDefaults'
import { getSystemLocale } from 'helpers/systemLocale'
import type { CurrencySettings } from 'types/common' import type { CurrencySettings } from 'types/common'
import type { State } from 'reducers' import type { State } from 'reducers'
@ -31,7 +32,8 @@ export type SettingsState = {
hasCompletedOnboarding: boolean, hasCompletedOnboarding: boolean,
counterValue: string, counterValue: string,
counterValueExchange: ?string, counterValueExchange: ?string,
language: string, language: ?string,
region: ?string,
orderAccounts: string, orderAccounts: string,
password: { password: {
isEnabled: boolean, isEnabled: boolean,
@ -42,22 +44,12 @@ export type SettingsState = {
currenciesSettings: { currenciesSettings: {
[currencyId: string]: CurrencySettings, [currencyId: string]: CurrencySettings,
}, },
region: string,
developerMode: boolean, developerMode: boolean,
shareAnalytics: boolean, shareAnalytics: boolean,
sentryLogs: boolean, sentryLogs: boolean,
lastUsedVersion: string, lastUsedVersion: string,
} }
/* have to check if available for all OS */
const localeSplit = (window.navigator.language || '').split('-')
let language = (localeSplit[0] || 'en').toLowerCase()
let region = (localeSplit[1] || 'US').toUpperCase()
if (!languages.includes(language)) {
language = 'en'
region = 'US'
}
const defaultsForCurrency: CryptoCurrency => CurrencySettings = crypto => { const defaultsForCurrency: CryptoCurrency => CurrencySettings = crypto => {
const defaults = currencySettingsDefaults(crypto) const defaults = currencySettingsDefaults(crypto)
return { return {
@ -70,7 +62,8 @@ const INITIAL_STATE: SettingsState = {
hasCompletedOnboarding: false, hasCompletedOnboarding: false,
counterValue: 'USD', counterValue: 'USD',
counterValueExchange: null, counterValueExchange: null,
language, language: null,
region: null,
orderAccounts: 'balance|asc', orderAccounts: 'balance|asc',
password: { password: {
isEnabled: false, isEnabled: false,
@ -79,10 +72,9 @@ const INITIAL_STATE: SettingsState = {
selectedTimeRange: 'month', selectedTimeRange: 'month',
marketIndicator: 'western', marketIndicator: 'western',
currenciesSettings: {}, currenciesSettings: {},
region,
developerMode: !!process.env.__DEV__, developerMode: !!process.env.__DEV__,
loaded: false, loaded: false,
shareAnalytics: false, shareAnalytics: true,
sentryLogs: true, sentryLogs: true,
lastUsedVersion: __APP_VERSION__, lastUsedVersion: __APP_VERSION__,
} }
@ -171,16 +163,28 @@ export const lastUsedVersionSelector = (state: State): string => state.settings.
export const availableCurrencies = createSelector(developerModeSelector, listCryptoCurrencies) export const availableCurrencies = createSelector(developerModeSelector, listCryptoCurrencies)
export const getLanguage = (state: State) => state.settings.language export const langAndRegionSelector = (state: State): { language: string, region: ?string } => {
let { language, region } = state.settings
export const localeSelector = (state: State) => { if (language && languages.includes(language)) {
const { language, region } = state.settings return { language, region }
if (!region) {
return language || 'en'
} }
return `${language || 'en'}-${region}` const locale = getSystemLocale()
language = locale.language
region = locale.region
if (!language || !languages.includes(language)) {
language = 'en'
region = 'US'
}
return { language, region }
} }
export const languageSelector = createSelector(langAndRegionSelector, o => o.language)
export const localeSelector = createSelector(
langAndRegionSelector,
({ language, region }) => (region ? `${language}-${region}` : language),
)
export const getOrderAccounts = (state: State) => state.settings.orderAccounts export const getOrderAccounts = (state: State) => state.settings.orderAccounts
export const areSettingsLoaded = (state: State) => state.settings.loaded export const areSettingsLoaded = (state: State) => state.settings.loaded
@ -217,6 +221,7 @@ export const exchangeSettingsForAccountSelector: ESFAS = createSelector(
export const marketIndicatorSelector = (state: State) => state.settings.marketIndicator export const marketIndicatorSelector = (state: State) => state.settings.marketIndicator
export const sentryLogsBooleanSelector = (state: State) => state.settings.sentryLogs export const sentryLogsBooleanSelector = (state: State) => state.settings.sentryLogs
export const shareAnalyticsSelector = (state: State) => state.settings.shareAnalytics
export const selectedTimeRangeSelector = (state: State) => state.settings.selectedTimeRange export const selectedTimeRangeSelector = (state: State) => state.settings.selectedTimeRange
export default handleActions(handlers, INITIAL_STATE) export default handleActions(handlers, INITIAL_STATE)

1
src/renderer/createStore.js

@ -22,6 +22,7 @@ export default ({ state, history, dbMiddleware }: Props) => {
const middlewares = [routerMiddleware(history), thunk, logger] const middlewares = [routerMiddleware(history), thunk, logger]
if (!process.env.STORYBOOK_ENV) { if (!process.env.STORYBOOK_ENV) {
middlewares.push(require('middlewares/sentry').default) middlewares.push(require('middlewares/sentry').default)
middlewares.push(require('middlewares/analytics').default)
} }
if (dbMiddleware) { if (dbMiddleware) {
middlewares.push(dbMiddleware) middlewares.push(dbMiddleware)

4
src/renderer/init.js

@ -17,7 +17,7 @@ import { enableGlobalTab, disableGlobalTab, isGlobalTabEnabled } from 'config/gl
import { fetchAccounts } from 'actions/accounts' import { fetchAccounts } from 'actions/accounts'
import { fetchSettings } from 'actions/settings' import { fetchSettings } from 'actions/settings'
import { isLocked } from 'reducers/application' import { isLocked } from 'reducers/application'
import { getLanguage, sentryLogsBooleanSelector } from 'reducers/settings' import { languageSelector, sentryLogsBooleanSelector } from 'reducers/settings'
import libcoreGetVersion from 'commands/libcoreGetVersion' import libcoreGetVersion from 'commands/libcoreGetVersion'
import db from 'helpers/db' import db from 'helpers/db'
@ -55,7 +55,7 @@ async function init() {
} }
const state = store.getState() const state = store.getState()
const language = getLanguage(state) const language = languageSelector(state)
moment.locale(language) moment.locale(language)
sentry(() => sentryLogsBooleanSelector(store.getState())) sentry(() => sentryLogsBooleanSelector(store.getState()))

11
static/analytics.min.js

File diff suppressed because one or more lines are too long

138
static/i18n/en/app.yml

@ -13,14 +13,14 @@ common:
selectAccount: Select an account selectAccount: Select an account
selectAccountNoOption: 'No account matching "{{accountName}}"' selectAccountNoOption: 'No account matching "{{accountName}}"'
selectCurrency: Choose a crypto asset selectCurrency: Choose a crypto asset
selectCurrencyNoOption: 'No currency matching "{{currencyName}}"' selectCurrencyNoOption: 'No crypto asset "{{currencyName}}"'
selectExchange: Select an exchange selectExchange: Select an exchange
selectExchangeNoOption: 'No exchange matching "{{exchangeName}}"' selectExchangeNoOption: 'No exchange matching "{{exchangeName}}"'
sortBy: Sort by sortBy: Sort by
search: Search search: Search
save: Save save: Save
password: Password password: Password
editProfile: Edit profile editProfile: Preferences
lockApplication: Lock Ledger Live lockApplication: Lock Ledger Live
showMore: Show more showMore: Show more
max: Max max: Max
@ -61,10 +61,10 @@ time:
month: Month month: Month
year: Year year: Year
since: since:
day: since a day day: past day
week: since a week week: past week
month: since a month month: past month
year: since a year year: past year
sidebar: sidebar:
menu: Menu menu: Menu
accounts: Accounts ({{count}}) accounts: Accounts ({{count}})
@ -75,20 +75,20 @@ account:
receive: Receive receive: Receive
lastOperations: Last operations lastOperations: Last operations
emptyState: emptyState:
title: No funds yet? title: No crypto assets yet?
desc: Make sure the <1><0>{{currency}}</0></1> app is installed to receive funds. # replace [cryptocurrency] and make it bold desc: Ensure the <1><0>{{currency}}</0></1> app is installed and start receiving
buttons: buttons:
receiveFunds: Receive funds receiveFunds: Receive
settings: settings:
title: Edit account title: Edit account
advancedLogs: Advanced logs advancedLogs: Advanced logs
accountName: accountName:
title: Account name title: Account name
desc: Describe this account. desc: Describe this account
error: An account name is required. error: An account name is required
unit: unit:
title: Unit title: Unit
desc: Choose the unit to display. desc: Choose the unit to display
endpointConfig: endpointConfig:
title: Node title: Node
desc: The API node to use desc: The API node to use
@ -96,16 +96,16 @@ account:
dashboard: dashboard:
title: Portfolio title: Portfolio
emptyAccountTile: emptyAccountTile:
desc: Lorem ipsum dolor sit amet, consectetur adipiscing elit desc: Add accounts to manage more crypto assets
createAccount: Create Account createAccount: Add account
accounts: accounts:
title: Accounts ({{count}}) title: Accounts ({{count}})
greeting: greeting:
morning: "Good morning" morning: "Good morning"
evening: "Good evening" evening: "Good evening"
afternoon: "Good afternoon" afternoon: "Good afternoon"
summary: "Here's the summary of your account." summary: "Here's the summary of your account"
summary_plural: "Here's the summary of your {{count}} accounts." summary_plural: "Here's the summary of your {{count}} accounts"
noAccounts: No accounts yet noAccounts: No accounts yet
recentActivity: Recent activity recentActivity: Recent activity
totalBalance: Total balance totalBalance: Total balance
@ -115,15 +115,15 @@ dashboard:
currentAddress: currentAddress:
title: Current address title: Current address
for: Address for <1><0>{{accountName}}</0></1> for: Address for <1><0>{{accountName}}</0></1>
message: Your receive address has not been confirmed on your Ledger device. Verify the address for optimal security. message: Your receive address has not been confirmed on your Ledger device. Please verify the 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:"
connect: Connect and unlock your <1>Ledger device</1> # remove key: <3>PIN code</3> connect: Connect and unlock your <1>Ledger device</1> # remove key: <3>PIN code</3>
dashboard: test dashboard: Not used. # This key is not used. Still managed in JS.
emptyState: emptyState:
sidebar: sidebar:
text: Press the + button to add an account to your portfolio. text: Press the + button to add an account to your portfolio
dashboard: dashboard:
title: 'Add accounts to your portfolio' title: 'Add accounts to your portfolio'
desc: Your portfolio has no accounts the first time Ledger Live is launched. Open the Manager to install apps on your Ledger device before you start adding accounts to your portfolio. desc: Your portfolio has no accounts the first time Ledger Live is launched. Open the Manager to install apps on your Ledger device before you start adding accounts to your portfolio.
@ -133,9 +133,9 @@ emptyState:
exchange: exchange:
title: Exchange title: Exchange
visitWebsite: Visit website visitWebsite: Visit website
coinhouse: 'Coinhouse is the trusted platform for individuals and institutional investors looking to analyze, acquire, sell and securely store cryptoassets.' coinhouse: 'Coinhouse is the trusted platform for individuals and institutional investors looking to analyze, acquire, sell and securely store crypto assets.'
changelly: 'Changelly is a popular instant cryptocurrency exchange with 100+ coins and tokens listed.' changelly: 'Changelly is a popular instant crypto asset exchange with 100+ coins and tokens listed.'
coinmama: 'Coinmama is a financial service that makes it fast, safe and fun to buy digital currency, anywhere in the world.' coinmama: 'Coinmama is a financial service that makes it fast, safe and fun to buy digital assets, anywhere in the world.'
genuinecheck: genuinecheck:
modal: modal:
title: Genuine check title: Genuine check
@ -153,13 +153,13 @@ addAccounts:
editName: Edit name editName: Edit name
newAccount: New account newAccount: New account
legacyAccount: '{{accountName}} (legacy)' legacyAccount: '{{accountName}} (legacy)'
noAccountToImport: All {{currencyName}} accounts found are already in your portfolio. noAccountToImport: No existing {{currencyName}} accounts to add
success: Account successfully added to your portfolio. success: Account added to your portfolio
# success_plural: Accounts successfully added to your portfolio. # success_plural: Accounts successfully added to your portfolio.
createNewAccount: createNewAccount:
title: Create new account title: Create new account
noOperationOnLastAccount: 'You have to receive funds on {{accountName}} before you can create a new account.' noOperationOnLastAccount: 'You have to receive crypto assets on {{accountName}} before you can create a new account.'
noAccountToCreate: No {{currencyName}} account was found to create. noAccountToCreate: No {{currencyName}} 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:
add: 'Add account' add: 'Add account'
@ -189,21 +189,21 @@ manager:
all: Apps all: Apps
installing: 'Installing {{app}}...' installing: 'Installing {{app}}...'
uninstalling: 'Uninstalling {{app}}...' uninstalling: 'Uninstalling {{app}}...'
installSuccess: '{{app}} is now installed on your device.' installSuccess: '{{app}} is now installed on your device'
uninstallSuccess: '{{app}} has been uninstalled from your device.' uninstallSuccess: '{{app}} has been uninstalled from your device'
alreadyInstalled: '{{app}} is already installed on your device.' alreadyInstalled: '{{app}} is already installed on your device'
help: Remove and reinstall to update apps help: Remove and reinstall to update apps
firmware: firmware:
installed: 'Firmware version {{version}}' installed: 'Firmware version {{version}}'
update: Update firmware update: Update firmware
updateTitle: Firmware update updateTitle: Firmware update
continue: Continue update continue: Continue update
latest: 'Firmware version {{version}} is available.' latest: 'Firmware version {{version}} is available'
disclaimerTitle: 'You are about to install the latest <1><0>firmware {{version}}</0></1>' disclaimerTitle: 'You are about to install the latest <1><0>firmware {{version}}</0></1>'
disclaimerAppDelete: Please note that all the apps installed on your device will be deleted. disclaimerAppDelete: Please note that all the apps installed on your device will be deleted.
disclaimerAppReinstall: You will be able to re-install your apps after the firmware update disclaimerAppReinstall: You will be able to re-install your apps after the firmware update
title: Manager title: Manager
subtitle: Install apps or update your device. subtitle: Select apps to use on your device
device: device:
title: Connect your device title: Connect your device
desc: Follow the steps below to use the Manager desc: Follow the steps below to use the Manager
@ -213,7 +213,7 @@ manager:
noDashboard: Navigate to the dashboard on your device (TEMPLATED NEEDED) noDashboard: Navigate to the dashboard on your device (TEMPLATED NEEDED)
noGenuine: Allow the Manager to continue (TEMPLATE NEEDED) noGenuine: Allow the Manager to continue (TEMPLATE NEEDED)
receive: receive:
title: Receive funds title: Receive
steps: steps:
chooseAccount: chooseAccount:
title: Choose account title: Choose account
@ -224,27 +224,27 @@ receive:
confirmAddress: confirmAddress:
title: Confirm address title: Confirm address
action: Confirm address on device action: Confirm address on device
text: To receive funds, confirm the address on your device. text: To receive crypto assets, confirm the address on your device
support: Ledger Support support: Ledger Support
error: error:
title: Receive address rejected title: Receive address rejected
text: Please try again or request Ledger Support assistance when in doubt. text: Please try again or contact Support when in doubt
receiveFunds: receiveFunds:
title: Receive funds title: Receive crypto assets
label: Amount (optional) label: Amount (optional)
send: send:
title: Send funds title: Send
totalSpent: Total totalSpent: Total
steps: steps:
amount: amount:
title: Create payment title: Send crypto assets
selectAccountDebit: Select an account to debit selectAccountDebit: Select an account to debit
recipientAddress: Recipient address # can't control the tooltip! recipientAddress: Recipient address # can't control the tooltip!
amount: Amount amount: Amount
max: Max max: Max
fees: Fees fees: Fees
advancedOptions: Advanced options advancedOptions: Advanced options
useRBF: Use the RBF transaction. useRBF: Use a replace-by-fee transaction
message: Leave a message (140) message: Leave a message (140)
rippleTag: Tag rippleTag: Tag
ethereumGasLimit: Gas limit ethereumGasLimit: Gas limit
@ -258,7 +258,7 @@ send:
Carefully verify the transaction details displayed one by one on your device before you proceed. Carefully verify the transaction details displayed one by one on your device before you proceed.
# blue # blue
# nano # nano
body: Press the right button to confirm the transaction. body: Press the right button to confirm the transaction
confirmation: confirmation:
title: Confirmation title: Confirmation
success: success:
@ -274,7 +274,7 @@ send:
releaseNotes: releaseNotes:
title: Release notes title: Release notes
version: Version {{versionNb}} version: Version {{versionNb}}
settings: settings: # Always ensure descriptions carry full stops (.)
title: Settings title: Settings
tabs: tabs:
display: Display display: Display
@ -282,29 +282,31 @@ settings:
profile: Profile profile: Profile
about: Help about: Help
display: display:
desc: desc: Control Ledger Live's appearance.
language: Language language: Display language
languageDesc: Choose the language to display. languageDesc: Set the language displayed in Ledger Live.
counterValue: Base currency counterValue: Base currency
counterValueDesc: Choose the currency to display next to your balance and operations. An exchange is used (via BTC pair). counterValueDesc: Choose the currency to display next to your balance and operations.
exchange: Rate provider ({{ticker}})
exchangeDesc: Choose the provider of the exchange rate between Bitcoin and your selected base currency.
region: Region region: Region
regionDesc: Choose the region in which you’re located to set the application’s time zone. regionDesc: Choose the region in which you’re located to set the Ledger Live's time zone.
stock: Regional market indicator stock: Regional market indicator
stockDesc: Choose Western to display an increase in market value in blue. Choose Eastern to display an increase in market value in red. stockDesc: Choose Western to display an increase in market value in blue. Choose Eastern to display an increase in market value in red.
currencies: currencies:
desc: Select a cryptocurrency to edit its settings. desc: Select a crypto asset to edit its settings.
exchange: Rate provider ({{ticker}}) exchange: Rate provider ({{ticker}})
exchangeDesc: Choose the provider of the base currency exchange rates (via BTC). exchangeDesc: Choose the provider of the rate between the selected crypto asset and Bitcoin. This intermediary calculation enables the calculation of your balance in your base currency, for indicative purposes only.
confirmationsToSpend: Number of confirmations required to spend confirmationsToSpend: Number of confirmations required to spend
confirmationsToSpendDesc: Set the number of confirmations required for your funds to be spendable. # A higher number of confirmations decreases the probability that a transaction is rejected. confirmationsToSpendDesc: Set the number of network confirmations required for your crypto assets to be spendable. # A higher number of confirmations decreases the probability that a transaction is rejected.
confirmationsNb: Number of confirmations confirmationsNb: Number of confirmations
confirmationsNbDesc: Set the number of blocks a transaction needs to be included in to consider it as confirmed. # A higher number of confirmations increases the certainty that a transaction cannot be reversed. confirmationsNbDesc: Set the number of network confirmations for a transaction to be marked as confirmed. # A higher number of confirmations increases the certainty that a transaction cannot be reversed.
transactionsFees: Default transaction fees transactionsFees: Default transaction fees
transactionsFeesDesc: Select your default transaction fees. The higher the fee, the quicker the transaction will be processed. transactionsFeesDesc: Select your default transaction fees. The higher the fee, the faster the transaction will be processed.
explorer: Blockchain explorer explorer: Blockchain explorer
explorerDesc: Choose which explorer is used to look up the operation details in the blockchain. explorerDesc: Choose which explorer is used to look up the operation details in the blockchain.
profile: profile:
desc: desc: Set the preferences for your profile.
password: Data encryption password: Data encryption
passwordDesc: Enhance your privacy. Set a password to encrypt Ledger Live data stored on your computer, including account names, balances, transactions and public addresses. passwordDesc: Enhance your privacy. Set a password to encrypt Ledger Live data stored on your computer, including account names, balances, transactions and public addresses.
changePassword: Change password changePassword: Change password
@ -316,16 +318,16 @@ settings:
softResetDesc: Clear the Ledger Live cache to force resynchronization with the blockchain. softResetDesc: Clear the Ledger Live cache to force resynchronization with the blockchain.
softReset: Clear softReset: Clear
hardResetTitle: Reset Ledger Live hardResetTitle: Reset Ledger Live
hardResetDesc: Erase all Ledger Live data stored on your computer, including your profile, accounts, transaction history and settings. The private keys that manage your crypto assets remain secure on your Ledger device. hardResetDesc: Erase all Ledger Live data stored on your computer, including your accounts, transaction history and settings. The private keys to access your crypto assets in the blockchain remain secure on your Ledger device and on your Recovery sheet.
hardReset: Reset hardReset: Reset
developerMode: Developer mode developerMode: Developer mode
developerModeDesc: Show developer apps in the Manager and enable testnet currencies. developerModeDesc: Show developer apps in the Manager and enable testnet apps.
analytics: Analytics analytics: Analytics
analyticsDesc: 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. analyticsDesc: 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.
reportErrors: Usage and diagnostics reportErrors: Usage and diagnostics
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: desc: Learn about Ledger Live features or get help.
version: Ledger Live version version: Ledger Live version
releaseNotesBtn: Show release notes # Close button instead of continue. releaseNotesBtn: Show release notes # Close button instead of continue.
faq: Ledger Support faq: Ledger Support
@ -336,35 +338,35 @@ settings:
termsDesc: --- Check with Legal --- termsDesc: --- Check with Legal ---
hardResetModal: hardResetModal:
title: Reset Ledger Live title: Reset Ledger Live
desc: Resetting will erase all Ledger Live data stored on your computer, including your profile, accounts, transaction history and application settings. The keys to access your crypto assets in the blockchain remain secure on your Ledger device. desc: Erase all Ledger Live data stored on your computer, including your accounts, transaction history and settings. The private keys to access your crypto assets in the blockchain remain secure on your Ledger device and on your Recovery sheet.
softResetModal: softResetModal:
title: Clear cache title: Clear cache
subTitle: Are you sure? subTitle: Are you sure?
desc: Clearing the Ledger Live cache forces network resynchronization. desc: Clearing the Ledger Live cache forces network resynchronization
removeAccountModal: removeAccountModal:
title: Remove account title: Remove account
subTitle: Are you sure? subTitle: Are you sure?
desc: The account will no longer be included in your portfolio. Accounts can always be re-added. desc: The account will no longer be included in your portfolio. This operation does not affect your assets. Accounts can always be re-added.
exportLogs: exportLogs:
title: Export logs title: Export logs
desc: Exporting Ledger Live logs may be necessary for troubleshooting purposes. desc: Exporting Ledger Live logs may be necessary for troubleshooting purposes.
btn: Export btn: Export
password: password:
warning_0: Warning 0 warning_0: Very weak
warning_1: Warning 1 warning_1: Weak
warning_2: Warning 2 warning_2: Medium
warning_3: Warning 3 warning_3: Strong
warning_4: Warning 4 warning_4: Very strong
errorMessageIncorrectPassword: The password you entered is incorrect. errorMessageIncorrectPassword: The password you entered is incorrect
errorMessageNotMatchingPassword: Passwords don't match. errorMessageNotMatchingPassword: Passwords don't match
inputFields: inputFields:
newPassword: newPassword:
label: Password label: Password
placeholder: placeholder: #remove
confirmPassword: confirmPassword:
label: Confirm password label: Confirm password
placeholder: placeholder: #remove
currentPassword: currentPassword: #remove
label: Current password label: Current password
placeholder: placeholder:
changePassword: changePassword:
@ -379,7 +381,7 @@ password:
title: Disable data encryption title: Disable data encryption
desc: Ledger Live data will be stored unencrypted on your computer. This includes account names, balances, transactions and public addresses. desc: Ledger Live data will be stored unencrypted on your computer. This includes account names, balances, transactions and public addresses.
update: update:
newVersionReady: A new update is available. newVersionReady: A new update is available
relaunch: Update now relaunch: Update now
crash: crash:
oops: Oops, something went wrong oops: Oops, something went wrong

8
static/i18n/en/errors.yml

@ -10,7 +10,7 @@ BtcUnmatchedApp: 'Open the ‘{{currencyName}}’ app on your Ledger device to p
WrongAppOpened: 'Open the ‘{{currencyName}}’ app on your Ledger device to proceed.' WrongAppOpened: 'Open the ‘{{currencyName}}’ app on your Ledger device to proceed.'
WrongDeviceForAccount: 'Use the device associated with the account ‘{{accountName}}’.' WrongDeviceForAccount: 'Use the device associated with the account ‘{{accountName}}’.'
LedgerAPINotAvailable: 'Ledger API not available for {{currencyName}}.' LedgerAPINotAvailable: 'Ledger API not available for {{currencyName}}.'
LedgerAPIError: 'Ledger API error. Try again later. (HTTP {{status}})' LedgerAPIError: 'Ledger API error. Try again. (HTTP {{status}})'
NetworkDown: 'Your internet connection seems down.' NetworkDown: 'Your internet connection seems down.'
NoAddressesFound: 'No accounts were found.' NoAddressesFound: 'No accounts were found.'
UserRefusedOnDevice: Transaction refused on device. UserRefusedOnDevice: Transaction refused on device.
@ -22,9 +22,9 @@ DeviceSocketNoHandler: Oops, device connection failed (handler {{query}}). Pleas
LatestMCUInstalledError: MCU on device already up to date. LatestMCUInstalledError: MCU on device already up to date.
HardResetFail: Reset failed. Please try again. HardResetFail: Reset failed. Please try again.
ManagerAPIsFail: Our services are currently unavailable. Please try again later. ManagerAPIsFail: Services are unavailable. Please try again.
ManagerUnexpected: Unexpected error occurred ({{msg}}). Please try again later. ManagerUnexpected: Unexpected error occurred ({{msg}}). Please try again.
ManagerNotEnoughSpace: Not enough room left on your device. Please uninstall some apps and try again. ManagerNotEnoughSpace: Not enough storage on device. Uninstall some apps and try again.
ManagerDeviceLocked: Device is locked ManagerDeviceLocked: Device is locked
ManagerAppAlreadyInstalled: App is already installed ManagerAppAlreadyInstalled: App is already installed
ManagerAppRelyOnBTC: You must install Bitcoin application first ManagerAppRelyOnBTC: You must install Bitcoin application first

1
static/i18n/en/language.yml

@ -1,2 +1,3 @@
system: Use system locale
en: English en: English
fr: French fr: French

16
static/i18n/en/onboarding.yml

@ -26,7 +26,7 @@ noDevice:
trackOrder: trackOrder:
title: Track your order title: Track your order
learnMore: learnMore:
title: Learn about Ledger Live title: Learn about Ledger
selectDevice: selectDevice:
title: Select your device title: Select your device
ledgerNanoCard: ledgerNanoCard:
@ -35,7 +35,7 @@ selectDevice:
title: Ledger Blue title: Ledger Blue
selectPIN: selectPIN:
disclaimer: disclaimer:
note1: Choose your own PIN code. This code will unlock your device. note1: 'Choose your own PIN code: this code will unlock your device.' # dotted line should be in red. Increase size of the hand (+50%?)
note2: An 8-digit PIN code offers an optimum level of security. note2: An 8-digit PIN code offers an optimum level of security.
note3: Never use a device supplied with a PIN code or a 24-word recovery phrase. note3: Never use a device supplied with a PIN code or a 24-word recovery phrase.
initialize: initialize:
@ -45,13 +45,13 @@ selectPIN:
step1: Connect the Ledger Nano S to your computer. step1: Connect the Ledger Nano S to your computer.
step2: Press both buttons simultaneously as instructed on the screen. step2: Press both buttons simultaneously as instructed on the screen.
step3: Press the right button to select Configure as new device?. # <bold>Configure as new device?<bold>. step3: Press the right button to select Configure as new device?. # <bold>Configure as new device?<bold>.
step4: 'Choose a PIN code between 4 and 8 digits long followed by the checkmark (✓).' step4: 'Choose a PIN code between 4 and 8 digits long, followed by the checkmark (✓).'
blue: blue:
step1: Connect the Ledger Blue to your computer. step1: Connect the Ledger Blue to your computer.
step2: Tap on Configure as new device. step2: Tap on Configure as new device.
step3: Choose a PIN code between 4 and 8 digits long. step3: Choose a PIN code between 4 and 8 digits long.
restore: restore:
title: Start restoration - Choose your PIN code title: Choose your PIN code
instructions: instructions:
nano: nano:
step1: Connect the Ledger Nano S to your computer. step1: Connect the Ledger Nano S to your computer.
@ -101,7 +101,7 @@ genuineCheck:
step2: step2:
title: Did you save your recovery phrase by yourself? title: Did you save your recovery phrase by yourself?
step3: step3:
title: Do you have a genuine Ledger device? title: Is your Ledger device genuine?
isGenuinePassed: Your device is genuine isGenuinePassed: Your device is genuine
buttons: buttons:
genuineCheck: Check now genuineCheck: Check now
@ -109,10 +109,10 @@ genuineCheck:
errorPage: errorPage:
ledgerNano: ledgerNano:
title: Oops, something went wrong... title: Oops, something went wrong...
desc: Go back to the security checklist or request Ledger Support assistance. desc: Go back to the security checklist or request Ledger Support assistance
ledgerBlue: ledgerBlue:
title: Oops, something went wrong... title: Oops, something went wrong...
desc: Go back to the security checklist or request Ledger Support assistance. desc: Go back to the security checklist or request Ledger Support assistance
setPassword: setPassword:
title: Encrypt Ledger Live data title: Encrypt Ledger Live data
desc: Set a password to encrypt Ledger Live data stored on your computer, including account names, balances, transactions and public addresses. desc: Set a password to encrypt Ledger Live data stored on your computer, including account names, balances, transactions and public addresses.
@ -131,7 +131,7 @@ 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 of anonymous data to help Ledger improve the user experience. This includes the operating system, language, firmware versions and the number of added accounts.
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
finish: finish:
title: Your device is ready! title: Your device is ready!
desc: Proceed to your porfolio and start adding your accounts... desc: Proceed to your porfolio and start adding your accounts...

2
static/i18n/fr/app.yml

@ -14,7 +14,7 @@ common:
selectAccount: Select an account selectAccount: Select an account
selectAccountNoOption: 'No account matching "{{accountName}}"' selectAccountNoOption: 'No account matching "{{accountName}}"'
selectCurrency: Choose a crypto asset selectCurrency: Choose a crypto asset
selectCurrencyNoOption: 'No currency matching "{{currencyName}}"' selectCurrencyNoOption: 'No crypto asset matching "{{currencyName}}"'
selectExchange: Select an exchange selectExchange: Select an exchange
selectExchangeNoOption: 'No exchange matching "{{exchangeName}}"' selectExchangeNoOption: 'No exchange matching "{{exchangeName}}"'
sortBy: Sort by sortBy: Sort by

Loading…
Cancel
Save