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. 51
      src/components/ExchangePage/ExchangeCard.js
  9. 44
      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. 38
      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. 30
      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 SyncOneAccountOnMount from 'components/SyncOneAccountOnMount'
import Tooltip from 'components/base/Tooltip'
import TrackPage from 'analytics/TrackPage'
import { MODAL_SEND, MODAL_RECEIVE, MODAL_SETTINGS_ACCOUNT } from 'config/constants'
@ -106,6 +107,13 @@ class AccountPage extends PureComponent<Props> {
return (
// Force re-render account page, for avoid animation
<Box key={account.id}>
<TrackPage
category="Account"
properties={{
currency: account.currency.id,
operationsLength: account.operations.length,
}}
/>
<SyncOneAccountOnMount priority={10} accountId={account.id} />
<Box horizontal mb={5} flow={4}>
<AccountHeader account={account} />

8
src/components/DashboardPage/index.js

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

51
src/components/ExchangePage/ExchangeCard.js

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

44
src/components/ExchangePage/index.js

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

14
src/components/OperationsList/index.js

@ -25,6 +25,7 @@ import IconAngleDown from 'icons/AngleDown'
import Box, { Card } from 'components/base/Box'
import Text from 'components/base/Text'
import Defer from 'components/base/Defer'
import Track from 'analytics/Track'
import SectionTitle from './SectionTitle'
import OperationC from './Operation'
@ -129,6 +130,19 @@ export class OperationsList extends PureComponent<Props, State> {
</Card>
</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 ? (
<ShowMore onClick={this.fetchMoreOperations}>
<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 { MODAL_RELEASES_NOTES } from 'config/constants'
import TrackPage from 'analytics/TrackPage'
import {
SettingsSection as Section,
@ -61,6 +62,7 @@ class SectionAbout extends PureComponent<Props> {
return (
<Section>
<TrackPage category="Settings" name="About" />
<Header
icon={<IconHelp size={16} />}
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 { currencySettingsDefaults } from 'helpers/SettingsDefaults'
import TrackPage from 'analytics/TrackPage'
import SelectCurrency from 'components/SelectCurrency'
import StepperNumber from 'components/base/StepperNumber'
import ExchangeSelect from 'components/SelectExchange'
@ -94,6 +95,7 @@ class TabCurrencies extends PureComponent<Props, State> {
const defaults = currencySettingsDefaults(currency)
return (
<Section key={currency.id}>
<TrackPage category="Settings" name="Currencies" />
<Header
icon={<IconCurrencies size={16} />}
title={t('app:settings.tabs.currencies')}

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

@ -13,6 +13,7 @@ import {
import type { SettingsState as Settings } from 'reducers/settings'
import type { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box'
import SelectExchange from 'components/SelectExchange'
import Select from 'components/base/Select'
@ -52,9 +53,9 @@ type Props = {
type State = {
cachedMarketIndicator: string,
cachedLanguageKey: string,
cachedLanguageKey: ?string,
cachedCounterValue: ?Object,
cachedRegion: string,
cachedRegion: ?string,
}
class TabProfile extends PureComponent<Props, State> {
@ -131,9 +132,12 @@ class TabProfile extends PureComponent<Props, State> {
const counterValueCurrency = counterValueCurrencyLocalSelector(settings)
const counterValueExchange = counterValueExchangeLocalSelector(settings)
const languages = languageKeys.map(key => ({ value: key, label: t(`language:${key}`) }))
const currentLanguage = languages.find(l => l.value === cachedLanguageKey)
const languages = [{ value: null, label: t(`language:system`) }].concat(
languageKeys.map(key => ({ value: key, label: t(`language:${key}`) })),
)
const regionsFiltered = regions.filter(({ language }) => cachedLanguageKey === language)
const currentLanguage = languages.find(l => l.value === cachedLanguageKey)
const currentRegion =
regionsFiltered.find(({ region }) => cachedRegion === region) || regionsFiltered[0]
@ -143,6 +147,7 @@ class TabProfile extends PureComponent<Props, State> {
return (
<Section>
<TrackPage category="Settings" name="Display" />
<Header
icon={<IconDisplay size={16} />}
title={t('app:settings.tabs.display')}
@ -187,16 +192,21 @@ class TabProfile extends PureComponent<Props, State> {
options={languages}
/>
</Row>
<Row title={t('app:settings.display.region')} desc={t('app:settings.display.regionDesc')}>
<Select
small
minWidth={250}
onChange={this.handleChangeRegion}
renderSelected={item => item && item.name}
value={currentRegion}
options={regionsFiltered}
/>
</Row>
{regionsFiltered.length === 0 ? null : (
<Row
title={t('app:settings.display.region')}
desc={t('app:settings.display.regionDesc')}
>
<Select
small
minWidth={250}
onChange={this.handleChangeRegion}
renderSelected={item => item && item.name}
value={currentRegion}
options={regionsFiltered}
/>
</Row>
)}
<Row title={t('app:settings.display.stock')} desc={t('app:settings.display.stockDesc')}>
<RadioGroup
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 { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import ExportLogsBtn from 'components/ExportLogsBtn'
import CheckBox from 'components/base/CheckBox'
import Box from 'components/base/Box'
@ -145,6 +146,7 @@ class TabProfile extends PureComponent<Props, State> {
const isPasswordEnabled = settings.password.isEnabled === true
return (
<Section>
<TrackPage category="Settings" name="Profile" />
<Header
icon={<IconUser size={16} />}
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 { createStructuredSelector } from 'reselect'
import Track from 'analytics/Track'
import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority'
import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
@ -227,6 +228,7 @@ class AddAccounts extends PureComponent<Props, State> {
steps={this.STEPS}
{...addtionnalProps}
>
<Track onUnmount event="CloseModalAddAccounts" />
<SyncSkipUnderPriority priority={100} />
</Stepper>
)}

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

@ -2,6 +2,7 @@
import React, { Fragment } from 'react'
import TrackPage from 'analytics/TrackPage'
import SelectCurrency from 'components/SelectCurrency'
import Button from 'components/base/Button'
import CurrencyBadge from 'components/base/CurrencyBadge'
@ -15,6 +16,7 @@ function StepChooseCurrency({ currency, setCurrency }: StepProps) {
export function StepChooseCurrencyFooter({ transitionTo, currency, t }: StepProps) {
return (
<Fragment>
<TrackPage category="AddAccounts" name="Step1" />
{currency && <CurrencyBadge mr="auto" currency={currency} />}
<Button primary disabled={!currency} onClick={() => transitionTo('connectDevice')}>
{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 { Trans } from 'react-i18next'
import TrackPage from 'analytics/TrackPage'
import Button from 'components/base/Button'
import Box from 'components/base/Box'
import ConnectDevice from 'components/modals/StepConnectDevice'
@ -12,10 +13,11 @@ import { CurrencyCircleIcon } from 'components/base/CurrencyBadge'
import type { StepProps } from '../index'
function StepConnectDevice({ t, currency, device, setAppOpened }: StepProps) {
invariant(currency, 'No currency given')
invariant(currency, 'No crypto asset given')
return (
<Fragment>
<TrackPage category="AddAccounts" name="Step2" />
<Box align="center" mb={6}>
<CurrencyCircleIcon mb={3} size={40} currency={currency} />
<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 TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box'
import CurrencyBadge from 'components/base/CurrencyBadge'
import Button from 'components/base/Button'
@ -199,6 +200,7 @@ class StepImport extends PureComponent<StepProps> {
return (
<Fragment>
<TrackPage category="AddAccounts" name="Step3" />
<Box flow={5}>
<AccountsList
title={importableAccountsListTitle}

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

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

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

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

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

@ -2,6 +2,7 @@
import React from 'react'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box'
import Label from 'components/base/Label'
import Button from 'components/base/Button'
@ -12,6 +13,7 @@ import type { StepProps } from '../index'
export default function StepAccount({ t, account, onChangeAccount }: StepProps) {
return (
<Box flow={1}>
<TrackPage category="Receive" name="Step1" />
<Label>{t('app:receive.steps.chooseAccount.label')}</Label>
<SelectAccount autoFocus onChange={onChangeAccount} value={account} />
</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 { isSegwitAccount } from 'helpers/bip32'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import DeviceConfirm from 'components/DeviceConfirm'
@ -52,6 +53,7 @@ export default class StepConfirmAddress extends PureComponent<StepProps> {
invariant(device, 'No device given')
return (
<Container>
<TrackPage category="Receive" name="Step3" />
{isAddressVerified === false ? (
<Fragment>
<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 React, { PureComponent } from 'react'
import TrackPage from 'analytics/TrackPage'
import Button from 'components/base/Button'
import Box from 'components/base/Box'
import Label from 'components/base/Label'
@ -33,6 +34,7 @@ export default class StepReceiveFunds extends PureComponent<StepProps, State> {
invariant(account, 'No account given')
return (
<Box flow={5}>
<TrackPage category="Receive" name="Step4" />
<Box flow={1}>
<Label>{t('app:receive.steps.receiveFunds.label')}</Label>
<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 { T } from 'types/common'
import type { WalletBridge } from 'bridge/types'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box'
import AccountField from './AccountField'
import RecipientField from './RecipientField'
@ -31,6 +32,8 @@ function StepAmount({
return (
<Box flow={4}>
<TrackPage category="Send" name="Step1" />
<AccountField t={t} onChange={onChangeAccount} value={account} />
{/* 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 styled from 'styled-components'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box'
import WarnBox from 'components/WarnBox'
import { multiline } from 'styles/helpers'
@ -32,6 +33,7 @@ type Props = {
export default ({ t }: Props) => (
<Container>
<TrackPage category="Send" name="Step3" />
<WarnBox>{multiline(t('app:send.steps.verification.warning'))}</WarnBox>
<Info>{t('app:send.steps.verification.body')}</Info>
<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 { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import Spinner from 'components/base/Spinner'
import IconCheckCircle from 'icons/CheckCircle'
import IconExclamationCircleThin from 'icons/ExclamationCircleThin'
@ -59,6 +60,7 @@ function StepConfirmation(props: Props) {
return (
<Container>
<TrackPage category="Send" name="Step4" />
<span style={{ color: iconColor }}>
<Icon size={43} />
</span>

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

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

1
src/components/modals/StepConnectDevice.js

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

1
src/config/constants.js

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

8
src/helpers/staticPath.js

@ -14,13 +14,15 @@ const staticPath =
? `${__dirname}/../../static`
: 'static'
export function getPath(path: string): string {
return isRunningInAsar ? `${staticPath}/${path}` : `/${path}`
}
/**
* Returns resolved static path for given image path
*
* note: `i` for `image` (using `img` was confusing when using with <img /> tag)
*/
export function i(path: string): string {
return isRunningInAsar ? `${staticPath}/images/${path}` : `/images/${path}`
}
export const i = (path: string): string => getPath(`images/${path}`)
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 { CryptoCurrency, Currency, Account } from '@ledgerhq/live-common/lib/types'
import { currencySettingsDefaults } from 'helpers/SettingsDefaults'
import { getSystemLocale } from 'helpers/systemLocale'
import type { CurrencySettings } from 'types/common'
import type { State } from 'reducers'
@ -31,7 +32,8 @@ export type SettingsState = {
hasCompletedOnboarding: boolean,
counterValue: string,
counterValueExchange: ?string,
language: string,
language: ?string,
region: ?string,
orderAccounts: string,
password: {
isEnabled: boolean,
@ -42,22 +44,12 @@ export type SettingsState = {
currenciesSettings: {
[currencyId: string]: CurrencySettings,
},
region: string,
developerMode: boolean,
shareAnalytics: boolean,
sentryLogs: boolean,
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 defaults = currencySettingsDefaults(crypto)
return {
@ -70,7 +62,8 @@ const INITIAL_STATE: SettingsState = {
hasCompletedOnboarding: false,
counterValue: 'USD',
counterValueExchange: null,
language,
language: null,
region: null,
orderAccounts: 'balance|asc',
password: {
isEnabled: false,
@ -79,10 +72,9 @@ const INITIAL_STATE: SettingsState = {
selectedTimeRange: 'month',
marketIndicator: 'western',
currenciesSettings: {},
region,
developerMode: !!process.env.__DEV__,
loaded: false,
shareAnalytics: false,
shareAnalytics: true,
sentryLogs: true,
lastUsedVersion: __APP_VERSION__,
}
@ -171,16 +163,28 @@ export const lastUsedVersionSelector = (state: State): string => state.settings.
export const availableCurrencies = createSelector(developerModeSelector, listCryptoCurrencies)
export const getLanguage = (state: State) => state.settings.language
export const localeSelector = (state: State) => {
const { language, region } = state.settings
if (!region) {
return language || 'en'
export const langAndRegionSelector = (state: State): { language: string, region: ?string } => {
let { language, region } = state.settings
if (language && languages.includes(language)) {
return { language, region }
}
const locale = getSystemLocale()
language = locale.language
region = locale.region
if (!language || !languages.includes(language)) {
language = 'en'
region = 'US'
}
return `${language || 'en'}-${region}`
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 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 sentryLogsBooleanSelector = (state: State) => state.settings.sentryLogs
export const shareAnalyticsSelector = (state: State) => state.settings.shareAnalytics
export const selectedTimeRangeSelector = (state: State) => state.settings.selectedTimeRange
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]
if (!process.env.STORYBOOK_ENV) {
middlewares.push(require('middlewares/sentry').default)
middlewares.push(require('middlewares/analytics').default)
}
if (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 { fetchSettings } from 'actions/settings'
import { isLocked } from 'reducers/application'
import { getLanguage, sentryLogsBooleanSelector } from 'reducers/settings'
import { languageSelector, sentryLogsBooleanSelector } from 'reducers/settings'
import libcoreGetVersion from 'commands/libcoreGetVersion'
import db from 'helpers/db'
@ -55,7 +55,7 @@ async function init() {
}
const state = store.getState()
const language = getLanguage(state)
const language = languageSelector(state)
moment.locale(language)
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
selectAccountNoOption: 'No account matching "{{accountName}}"'
selectCurrency: Choose a crypto asset
selectCurrencyNoOption: 'No currency matching "{{currencyName}}"'
selectCurrencyNoOption: 'No crypto asset "{{currencyName}}"'
selectExchange: Select an exchange
selectExchangeNoOption: 'No exchange matching "{{exchangeName}}"'
sortBy: Sort by
search: Search
save: Save
password: Password
editProfile: Edit profile
editProfile: Preferences
lockApplication: Lock Ledger Live
showMore: Show more
max: Max
@ -61,10 +61,10 @@ time:
month: Month
year: Year
since:
day: since a day
week: since a week
month: since a month
year: since a year
day: past day
week: past week
month: past month
year: past year
sidebar:
menu: Menu
accounts: Accounts ({{count}})
@ -75,20 +75,20 @@ account:
receive: Receive
lastOperations: Last operations
emptyState:
title: No funds yet?
desc: Make sure the <1><0>{{currency}}</0></1> app is installed to receive funds. # replace [cryptocurrency] and make it bold
title: No crypto assets yet?
desc: Ensure the <1><0>{{currency}}</0></1> app is installed and start receiving
buttons:
receiveFunds: Receive funds
receiveFunds: Receive
settings:
title: Edit account
advancedLogs: Advanced logs
accountName:
title: Account name
desc: Describe this account.
error: An account name is required.
desc: Describe this account
error: An account name is required
unit:
title: Unit
desc: Choose the unit to display.
desc: Choose the unit to display
endpointConfig:
title: Node
desc: The API node to use
@ -96,16 +96,16 @@ account:
dashboard:
title: Portfolio
emptyAccountTile:
desc: Lorem ipsum dolor sit amet, consectetur adipiscing elit
createAccount: Create Account
desc: Add accounts to manage more crypto assets
createAccount: Add account
accounts:
title: Accounts ({{count}})
greeting:
morning: "Good morning"
evening: "Good evening"
afternoon: "Good afternoon"
summary: "Here's the summary of your account."
summary_plural: "Here's the summary of your {{count}} accounts."
summary: "Here's the summary of your account"
summary_plural: "Here's the summary of your {{count}} accounts"
noAccounts: No accounts yet
recentActivity: Recent activity
totalBalance: Total balance
@ -115,15 +115,15 @@ dashboard:
currentAddress:
title: Current address
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:
step1:
choose: "We detected {{count}} connected devices, please select one:"
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:
sidebar:
text: Press the + button to add an account to your portfolio.
text: Press the + button to add an account to your portfolio
dashboard:
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.
@ -133,9 +133,9 @@ emptyState:
exchange:
title: Exchange
visitWebsite: Visit website
coinhouse: 'Coinhouse is the trusted platform for individuals and institutional investors looking to analyze, acquire, sell and securely store cryptoassets.'
changelly: 'Changelly is a popular instant cryptocurrency 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.'
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 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 assets, anywhere in the world.'
genuinecheck:
modal:
title: Genuine check
@ -153,13 +153,13 @@ addAccounts:
editName: Edit name
newAccount: New account
legacyAccount: '{{accountName}} (legacy)'
noAccountToImport: All {{currencyName}} accounts found are already in your portfolio.
success: Account successfully added to your portfolio.
noAccountToImport: No existing {{currencyName}} accounts to add
success: Account added to your portfolio
# success_plural: Accounts successfully added to your portfolio.
createNewAccount:
title: Create new account
noOperationOnLastAccount: 'You have to receive funds on {{accountName}} before you can create a new account.'
noAccountToCreate: No {{currencyName}} account was found to create.
noOperationOnLastAccount: 'You have to receive crypto assets on {{accountName}} before you can create a new account.'
noAccountToCreate: No {{currencyName}} account was found to create
somethingWentWrong: Something went wrong during synchronization, please try again.
cta:
add: 'Add account'
@ -189,21 +189,21 @@ manager:
all: Apps
installing: 'Installing {{app}}...'
uninstalling: 'Uninstalling {{app}}...'
installSuccess: '{{app}} is now installed on your device.'
uninstallSuccess: '{{app}} has been uninstalled from your device.'
alreadyInstalled: '{{app}} is already installed on your device.'
installSuccess: '{{app}} is now installed on your device'
uninstallSuccess: '{{app}} has been uninstalled from your device'
alreadyInstalled: '{{app}} is already installed on your device'
help: Remove and reinstall to update apps
firmware:
installed: 'Firmware version {{version}}'
update: Update firmware
updateTitle: Firmware 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>'
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
title: Manager
subtitle: Install apps or update your device.
subtitle: Select apps to use on your device
device:
title: Connect your device
desc: Follow the steps below to use the Manager
@ -213,7 +213,7 @@ manager:
noDashboard: Navigate to the dashboard on your device (TEMPLATED NEEDED)
noGenuine: Allow the Manager to continue (TEMPLATE NEEDED)
receive:
title: Receive funds
title: Receive
steps:
chooseAccount:
title: Choose account
@ -224,27 +224,27 @@ receive:
confirmAddress:
title: Confirm address
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
error:
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:
title: Receive funds
title: Receive crypto assets
label: Amount (optional)
send:
title: Send funds
title: Send
totalSpent: Total
steps:
amount:
title: Create payment
title: Send crypto assets
selectAccountDebit: Select an account to debit
recipientAddress: Recipient address # can't control the tooltip!
amount: Amount
max: Max
fees: Fees
advancedOptions: Advanced options
useRBF: Use the RBF transaction.
useRBF: Use a replace-by-fee transaction
message: Leave a message (140)
rippleTag: Tag
ethereumGasLimit: Gas limit
@ -258,7 +258,7 @@ send:
Carefully verify the transaction details displayed one by one on your device before you proceed.
# blue
# nano
body: Press the right button to confirm the transaction.
body: Press the right button to confirm the transaction
confirmation:
title: Confirmation
success:
@ -274,7 +274,7 @@ send:
releaseNotes:
title: Release notes
version: Version {{versionNb}}
settings:
settings: # Always ensure descriptions carry full stops (.)
title: Settings
tabs:
display: Display
@ -282,29 +282,31 @@ settings:
profile: Profile
about: Help
display:
desc:
language: Language
languageDesc: Choose the language to display.
desc: Control Ledger Live's appearance.
language: Display language
languageDesc: Set the language displayed in Ledger Live.
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
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
stockDesc: Choose Western to display an increase in market value in blue. Choose Eastern to display an increase in market value in red.
currencies:
desc: Select a cryptocurrency to edit its settings.
desc: Select a crypto asset to edit its settings.
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
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
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
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
explorerDesc: Choose which explorer is used to look up the operation details in the blockchain.
profile:
desc:
desc: Set the preferences for your profile.
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.
changePassword: Change password
@ -316,16 +318,16 @@ settings:
softResetDesc: Clear the Ledger Live cache to force resynchronization with the blockchain.
softReset: Clear
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
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
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
reportErrorsDesc: Share anonymous usage and diagnostics data to help improve Ledger products, services and security features.
about:
desc:
desc: Learn about Ledger Live features or get help.
version: Ledger Live version
releaseNotesBtn: Show release notes # Close button instead of continue.
faq: Ledger Support
@ -336,35 +338,35 @@ settings:
termsDesc: --- Check with Legal ---
hardResetModal:
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:
title: Clear cache
subTitle: Are you sure?
desc: Clearing the Ledger Live cache forces network resynchronization.
desc: Clearing the Ledger Live cache forces network resynchronization
removeAccountModal:
title: Remove account
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:
title: Export logs
desc: Exporting Ledger Live logs may be necessary for troubleshooting purposes.
btn: Export
password:
warning_0: Warning 0
warning_1: Warning 1
warning_2: Warning 2
warning_3: Warning 3
warning_4: Warning 4
errorMessageIncorrectPassword: The password you entered is incorrect.
errorMessageNotMatchingPassword: Passwords don't match.
warning_0: Very weak
warning_1: Weak
warning_2: Medium
warning_3: Strong
warning_4: Very strong
errorMessageIncorrectPassword: The password you entered is incorrect
errorMessageNotMatchingPassword: Passwords don't match
inputFields:
newPassword:
label: Password
placeholder:
placeholder: #remove
confirmPassword:
label: Confirm password
placeholder:
currentPassword:
placeholder: #remove
currentPassword: #remove
label: Current password
placeholder:
changePassword:
@ -379,7 +381,7 @@ password:
title: Disable data encryption
desc: Ledger Live data will be stored unencrypted on your computer. This includes account names, balances, transactions and public addresses.
update:
newVersionReady: A new update is available.
newVersionReady: A new update is available
relaunch: Update now
crash:
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.'
WrongDeviceForAccount: 'Use the device associated with the account ‘{{accountName}}’.'
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.'
NoAddressesFound: 'No accounts were found.'
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.
HardResetFail: Reset failed. Please try again.
ManagerAPIsFail: Our services are currently unavailable. Please try again later.
ManagerUnexpected: Unexpected error occurred ({{msg}}). Please try again later.
ManagerNotEnoughSpace: Not enough room left on your device. Please uninstall some apps and try again.
ManagerAPIsFail: Services are unavailable. Please try again.
ManagerUnexpected: Unexpected error occurred ({{msg}}). Please try again.
ManagerNotEnoughSpace: Not enough storage on device. Uninstall some apps and try again.
ManagerDeviceLocked: Device is locked
ManagerAppAlreadyInstalled: App is already installed
ManagerAppRelyOnBTC: You must install Bitcoin application first

1
static/i18n/en/language.yml

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

16
static/i18n/en/onboarding.yml

@ -26,7 +26,7 @@ noDevice:
trackOrder:
title: Track your order
learnMore:
title: Learn about Ledger Live
title: Learn about Ledger
selectDevice:
title: Select your device
ledgerNanoCard:
@ -35,7 +35,7 @@ selectDevice:
title: Ledger Blue
selectPIN:
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.
note3: Never use a device supplied with a PIN code or a 24-word recovery phrase.
initialize:
@ -45,13 +45,13 @@ selectPIN:
step1: Connect the Ledger Nano S to your computer.
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>.
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:
step1: Connect the Ledger Blue to your computer.
step2: Tap on Configure as new device.
step3: Choose a PIN code between 4 and 8 digits long.
restore:
title: Start restoration - Choose your PIN code
title: Choose your PIN code
instructions:
nano:
step1: Connect the Ledger Nano S to your computer.
@ -101,7 +101,7 @@ genuineCheck:
step2:
title: Did you save your recovery phrase by yourself?
step3:
title: Do you have a genuine Ledger device?
title: Is your Ledger device genuine?
isGenuinePassed: Your device is genuine
buttons:
genuineCheck: Check now
@ -109,10 +109,10 @@ genuineCheck:
errorPage:
ledgerNano:
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:
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:
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.
@ -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.
sentryLogs:
title: Report bugs
desc: Automatically send reports to help Ledger fix bugs.
desc: Automatically send reports to help Ledger fix bugs
finish:
title: Your device is ready!
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
selectAccountNoOption: 'No account matching "{{accountName}}"'
selectCurrency: Choose a crypto asset
selectCurrencyNoOption: 'No currency matching "{{currencyName}}"'
selectCurrencyNoOption: 'No crypto asset matching "{{currencyName}}"'
selectExchange: Select an exchange
selectExchangeNoOption: 'No exchange matching "{{exchangeName}}"'
sortBy: Sort by

Loading…
Cancel
Save