Skip to content
This repository was archived by the owner on Apr 17, 2023. It is now read-only.

Internationalization Enhancement #2074

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/js/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import NotificationsSystem from 'reapop'
import NotificationsTheme from 'reapop-theme-wybo'
import { hot } from 'react-hot-loader'
import { selectLastUpdatedApps } from './store/apps/selectors'

import log4js from 'log4js'

const logger = log4js.getLogger(__filename)
Expand Down
26 changes: 16 additions & 10 deletions app/js/HomeScreenPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { Box, Flex, Type } from 'blockstack-ui'
import { Hover } from 'react-powerplug'
import { Spinner } from '@components/ui/components/spinner'
import { trackEventOnce } from './utils/server-utils'
import { withTranslation } from 'react-i18next'

const Loading = ({ ...rest }) => (
const Loading = ({ t, ...rest }) => (
<Flex
flexDirection="column"
alignItems="center"
Expand All @@ -23,24 +24,24 @@ const Loading = ({ ...rest }) => (
</Box>
<Box py={4} textAlign="center">
<Type fontWeight="500" fontSize={2} opacity={0.5}>
Fetching apps...
{t('fetching_apps')}
</Type>
</Box>
</Flex>
)

const Content = ({ topApps, allApps, ...rest }) => {
const Content = ({ t, topApps, allApps, ...rest }) => {
if (!allApps) {
return null
}
return (
<Box maxWidth={1280} width="100%" mx="auto" p={[1, 2, 4]} {...rest}>
<AppsSection title="Top Apps" apps={topApps} limit={24} />
<AppsSection title={t('top_apps')} apps={topApps} limit={24} />
{allApps
.sort((a, b) => a.label.localeCompare(b.label))
.map(category => {
.map((category, idx) => {
const apps = category.apps.sort((a, b) => a.name.localeCompare(b.name))
return <AppsSection title={category.label} apps={apps} />
return <AppsSection key={idx} title={category.label} apps={apps} />
})}
</Box>
)
Expand All @@ -67,9 +68,9 @@ const AppsSection = ({ title, apps, limit, category, ...rest }) => {
<p className="app-section-heading">{title}</p>
</Box>
<Flex pt={4} flexWrap="wrap" justifyContent={['center', 'space-between']}>
{appsList.map(app => (
{appsList.map((app, idx) => (
<AppItem
key={app.name}
key={idx}
name={app.name}
imgixImageUrl={app.imgixImageUrl}
website={app.website}
Expand Down Expand Up @@ -191,14 +192,17 @@ class HomeScreenPage extends React.Component {
this.props.apps.loading ||
!this.props.apps.topApps ||
!this.props.apps.topApps.length

const { t } = this.props
return (
<Box>
<Navbar hideBackToHomeLink activeTab="home" />
<Box className="home-screen">
{loading ? (
<Loading />
<Loading t={t} />
) : (
<Content
t={t}
allApps={!loading && this.props.apps.appsByCategory}
topApps={!loading && this.props.apps.topApps}
/>
Expand All @@ -210,6 +214,7 @@ class HomeScreenPage extends React.Component {
}

Content.propTypes = {
t: PropTypes.func.isRequired,
topApps: PropTypes.array.isRequired,
allApps: PropTypes.array.isRequired
}
Expand All @@ -228,6 +233,7 @@ AppItem.propTypes = {
}

HomeScreenPage.propTypes = {
t: PropTypes.func.isRequired,
apps: PropTypes.object.isRequired,
refreshAppList: PropTypes.func.isRequired,
doFetchApps: PropTypes.func.isRequired,
Expand All @@ -251,4 +257,4 @@ const mapDispatchToProps = dispatch =>
export default connect(
mapStateToProps,
mapDispatchToProps
)(HomeScreenPage)
)(withTranslation()(HomeScreenPage))
4 changes: 2 additions & 2 deletions app/js/account/store/account/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -450,14 +450,14 @@ function refreshBalances(balanceURL, addresses) {
})

if (results.length >= addresses.length) {
let balances = {}
const balances = {}
let total = 0.0

for (let i = 0; i < results.length; i++) {
const thisAddress = results[i].address
if (!balances.hasOwnProperty(thisAddress)) {
const balance = results[i].balance
total = total + balance
total += balance
balances[thisAddress] = balance
} else {
logger.error(
Expand Down
2 changes: 1 addition & 1 deletion app/js/account/store/settings/default.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as crypto from 'crypto'
import { BLOCKSTACK_INC } from '../../../account/utils/index'
import { BLOCKSTACK_INC } from '../../utils/index'

export const REGTEST_CORE_API_PASSWORD = 'blockstack_integration_test_api_password'
export const DEFAULT_CORE_PHONY_PASSWORD = 'PretendPasswordAPI'
Expand Down
9 changes: 5 additions & 4 deletions app/js/components/Navbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,32 @@ import { Hover, Focus } from 'react-powerplug'
import { Box, Flex, Type } from 'blockstack-ui'
import { Link, withRouter } from 'react-router'
import PropTypes from 'prop-types'
import i18n from '../i18n'

const navBarData = [
[
{
label: 'Home',
label: i18n.t('home'),
icon: HomeIcon,
path: '/',
active: '/'
}
],
[
{
label: 'Identity',
label: i18n.t('identity'),
icon: IDsIcon,
path: '/profiles',
active: 'profiles'
},
{
label: 'Wallet',
label: i18n.t('wallet'),
icon: WalletIcon,
path: '/wallet/receive',
active: 'wallet'
},
{
label: 'Settings',
label: i18n.t('settings'),
icon: SettingsIcon,
path: '/account',
active: 'account'
Expand Down
79 changes: 79 additions & 0 deletions app/js/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import i18next from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import { initReactI18next } from 'react-i18next'
import en from './translations/en/translations.json'
import fr from './translations/fr/translations.json'
const resources = {
en: { translation: en },
fr: { translation: fr }
}
/**
* DESC
* configuration for i18next
*
* USAGE
* import in index.js (import './i18n';)
*
*/

const options = {
// order and from where user language should be detected
order: ['path', 'querystring', 'cookie', 'localStorage', 'sessionStorage', 'navigator', 'htmlTag', 'subdomain'],
// keys or params to lookup language from
lookupQuerystring: 'lng',
lookupCookie: 'i18next',
lookupLocalStorage: 'i18nextLng',
lookupSessionStorage: 'i18nextLng',
lookupFromPathIndex: 0,
lookupFromSubdomainIndex: 0,
// cache user language on
caches: ['localStorage', 'cookie'],
excludeCacheFor: ['cimode'], // languages to not persist (cookie, localStorage)
// optional expire and domain for set cookie
cookieMinutes: 10,
cookieDomain: 'myDomain',
// optional htmlTag with lang attribute, the default is:
htmlTag: document.documentElement,
// optional set cookie options, reference:[MDN Set-Cookie docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)
cookieOptions: { path: '/', sameSite: 'strict' }
}
i18next
// detects user language:
// based on browsers set language or on querystring ?lng=LANGUAGE
// @see https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
resources,
// lng: 'fr', //hardcoded
compatibilityJSON: 'v2',
fallbackLng: 'en',
debug: false,
whitelist: ['en', 'fr'], // to be moved to config file, depends on the languages available
load: 'languageOnly',
detection: options,
interpolation: {
escapeValue: false // not needed for react as it escapes by default
},
// react specific
// @see https://react.i18next.com/latest/i18next-instance
react: {
bindI18n: 'languageChanged',
bindI18nStore: '',
transEmptyNodeValue: '',
transSupportBasicHtmlNodes: true, // allow <br/> and simple html elements in translations
transKeepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'], // don't convert to <1></1> if simple react elements
useSuspense: false
}
}, err => {
if (err) {
console.error('Error loading translation files', err)
return
}
})
.then(() => {
document.documentElement.lang = i18next.language
})
export default i18next
2 changes: 1 addition & 1 deletion app/js/profiles/components/EditSocialAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import InputGroup from '@components/InputGroup'
import VerificationInfo from '../components/VerificationInfo'
import VerificationInfo from './VerificationInfo'
import { openInNewTab, getWebAccountTypes } from '../../utils'

const helpPages = {
Expand Down
2 changes: 1 addition & 1 deletion app/js/profiles/components/EditSocialAccountItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { debounce } from 'lodash'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'

import InputGroup from '@components/InputGroup'
import VerificationInfo from '../components/VerificationInfo'
import VerificationInfo from './VerificationInfo'

import { getWebAccountTypes } from '../../utils'

Expand Down
8 changes: 8 additions & 0 deletions app/js/translations/en/translations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"fetching_apps": "Fetching apps...",
"home": "Home",
"identity": "Identity",
"settings": "Settings",
"top_apps": "Top Apps",
"wallet": "Wallet"
}
8 changes: 8 additions & 0 deletions app/js/translations/fr/translations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"fetching_apps": "Récupérer des applications...",
"home": "Accueil",
"identity": "Identité",
"settings": "Paramètres",
"top_apps": "Meilleures applications",
"wallet": "Portefeuille"
}
Loading