Skip to content

Commit d7106fe

Browse files
committed
🔀 merge from develop
2 parents e78d2d9 + 3e66c73 commit d7106fe

26 files changed

+320
-102
lines changed

client/components/AddRemoveButton.jsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
3+
import { withTranslation } from 'react-i18next';
4+
35

46
import AddIcon from '../images/plus.svg';
57
import RemoveIcon from '../images/minus.svg';
68

7-
const AddRemoveButton = ({ type, onClick }) => {
8-
const alt = type === 'add' ? 'Add to collection' : 'Remove from collection';
9+
const AddRemoveButton = ({ type, onClick, t }) => {
10+
const alt = type === 'add' ? t('AddRemoveButton.AltAddARIA') : t('AddRemoveButton.AltRemoveARIA');
911
const Icon = type === 'add' ? AddIcon : RemoveIcon;
1012

1113
return (
@@ -22,6 +24,7 @@ const AddRemoveButton = ({ type, onClick }) => {
2224
AddRemoveButton.propTypes = {
2325
type: PropTypes.oneOf(['add', 'remove']).isRequired,
2426
onClick: PropTypes.func.isRequired,
27+
t: PropTypes.func.isRequired
2528
};
2629

27-
export default AddRemoveButton;
30+
export default withTranslation()(AddRemoveButton);

client/components/Nav.jsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { withRouter } from 'react-router';
55
import { Link } from 'react-router';
66
import classNames from 'classnames';
77
import { withTranslation } from 'react-i18next';
8-
import i18next from 'i18next';
8+
import { languageKeyToLabel } from '../i18n';
99
import * as IDEActions from '../modules/IDE/actions/ide';
1010
import * as toastActions from '../modules/IDE/actions/toast';
1111
import * as projectActions from '../modules/IDE/actions/project';
@@ -549,7 +549,7 @@ class Nav extends React.PureComponent {
549549

550550
renderLanguageMenu(navDropdownState) {
551551
return (
552-
<ul className="nav__items-right" title="user-menu">
552+
<React.Fragment>
553553
<li className={navDropdownState.lang}>
554554
<button
555555
onClick={this.toggleDropdownForLang}
@@ -561,7 +561,7 @@ class Nav extends React.PureComponent {
561561
}
562562
}}
563563
>
564-
<span className="nav__item-header"> {this.props.t('Nav.Lang')}</span>
564+
<span className="nav__item-header"> {languageKeyToLabel(this.props.language)}</span>
565565
<TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" />
566566
</button>
567567
<ul className="nav__dropdown">
@@ -597,14 +597,15 @@ class Nav extends React.PureComponent {
597597
</li>
598598
</ul>
599599
</li>
600-
</ul>
600+
</React.Fragment>
601601
);
602602
}
603603

604604

605605
renderUnauthenticatedUserMenu(navDropdownState) {
606606
return (
607607
<ul className="nav__items-right" title="user-menu">
608+
{getConfig('TRANSLATIONS_ENABLED') && this.renderLanguageMenu(navDropdownState)}
608609
<li className="nav__item">
609610
<Link to="/login" className="nav__auth-button">
610611
<span className="nav__item-header">{this.props.t('Nav.Login')}</span>
@@ -623,10 +624,7 @@ class Nav extends React.PureComponent {
623624
renderAuthenticatedUserMenu(navDropdownState) {
624625
return (
625626
<ul className="nav__items-right" title="user-menu">
626-
<li className="nav__item">
627-
<span>{this.props.t('Nav.Auth.Hello')}, {this.props.user.username}!</span>
628-
</li>
629-
<span className="nav__item-spacer">|</span>
627+
{getConfig('TRANSLATIONS_ENABLED') && this.renderLanguageMenu(navDropdownState)}
630628
<li className={navDropdownState.account}>
631629
<button
632630
className="nav__item-header"
@@ -639,7 +637,7 @@ class Nav extends React.PureComponent {
639637
}
640638
}}
641639
>
642-
{this.props.t('Nav.Auth.MyAccount')}
640+
<span>{this.props.t('Nav.Auth.Hello')}, {this.props.user.username}!</span>
643641
<TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" />
644642
</button>
645643
<ul className="nav__dropdown">
@@ -755,7 +753,6 @@ class Nav extends React.PureComponent {
755753
<header>
756754
<nav className="nav" title="main-navigation" ref={(node) => { this.node = node; }}>
757755
{this.renderLeftLayout(navDropdownState)}
758-
{getConfig('TRANSLATIONS_ENABLED') && this.renderLanguageMenu(navDropdownState)}
759756
{this.renderUserMenu(navDropdownState)}
760757
</nav>
761758
</header>
@@ -809,6 +806,7 @@ Nav.propTypes = {
809806
}),
810807
t: PropTypes.func.isRequired,
811808
setLanguage: PropTypes.func.isRequired,
809+
language: PropTypes.string.isRequired,
812810
};
813811

814812
Nav.defaultProps = {
@@ -829,7 +827,8 @@ function mapStateToProps(state) {
829827
project: state.project,
830828
user: state.user,
831829
unsavedChanges: state.ide.unsavedChanges,
832-
rootFile: state.files.filter(file => file.name === 'root')[0]
830+
rootFile: state.files.filter(file => file.name === 'root')[0],
831+
language: state.preferences.language
833832
};
834833
}
835834

client/components/PreviewNav.jsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
import PropTypes from 'prop-types';
22
import React from 'react';
33
import { Link } from 'react-router';
4+
import { withTranslation } from 'react-i18next';
45

56
import LogoIcon from '../images/p5js-logo-small.svg';
67
import CodeIcon from '../images/code.svg';
78

8-
const PreviewNav = ({ owner, project }) => (
9+
const PreviewNav = ({ owner, project, t }) => (
910
<nav className="nav preview-nav">
1011
<div className="nav__items-left">
1112
<div className="nav__item-logo">
12-
<LogoIcon role="img" aria-label="p5.js Logo" focusable="false" className="svg__logo" />
13+
<LogoIcon role="img" aria-label={t('Common.p5logoARIA')} focusable="false" className="svg__logo" />
1314
</div>
1415
<Link className="nav__item" to={`/${owner.username}/sketches/${project.id}`}>{project.name}</Link>
15-
<p className="toolbar__project-owner">by</p>
16+
<p className="toolbar__project-owner">{t('PreviewNav.ByUser')}</p>
1617
<Link className="nav__item" to={`/${owner.username}/sketches/`}>{owner.username}</Link>
1718
</div>
1819
<div className="nav__items-right">
19-
<Link to={`/${owner.username}/sketches/${project.id}`} aria-label="Edit Sketch" >
20+
<Link to={`/${owner.username}/sketches/${project.id}`} aria-label={t('PreviewNav.EditSketchARIA')} >
2021
<CodeIcon className="preview-nav__editor-svg" focusable="false" aria-hidden="true" />
2122
</Link>
2223
</div>
@@ -31,6 +32,7 @@ PreviewNav.propTypes = {
3132
name: PropTypes.string.isRequired,
3233
id: PropTypes.string.isRequired,
3334
}).isRequired,
35+
t: PropTypes.func.isRequired
3436
};
3537

36-
export default PreviewNav;
38+
export default withTranslation()(PreviewNav);

client/components/__test__/Nav.test.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ describe('Nav', () => {
4646
id: 'root-file'
4747
},
4848
t: jest.fn(),
49-
setLanguage: jest.fn()
49+
setLanguage: jest.fn(),
50+
language: 'en-US'
5051
};
5152

5253
it('renders correctly', () => {

client/i18n-test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import i18n from 'i18next';
2+
import { initReactI18next } from 'react-i18next';
3+
4+
import translations from '../translations/locales/en-US/translations.json';
5+
6+
i18n
7+
.use(initReactI18next)
8+
.init({
9+
lng: 'en-US',
10+
fallbackLng: 'en-US',
11+
12+
// have a common namespace used around the full app
13+
ns: ['translations'],
14+
defaultNS: 'translations',
15+
16+
debug: false,
17+
18+
interpolation: {
19+
escapeValue: false, // not needed for react!!
20+
},
21+
22+
resources: { 'en-US': { translations } },
23+
});
24+
25+
26+
export default i18n;

client/i18n.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ import Backend from 'i18next-http-backend';
66
const fallbackLng = ['en-US'];
77
const availableLanguages = ['en-US', 'es-419'];
88

9+
export function languageKeyToLabel(lang) {
10+
const languageMap = {
11+
'en-US': 'English',
12+
'es-419': 'Español'
13+
};
14+
return languageMap[lang];
15+
}
16+
917
const options = {
1018
loadPath: '/locales/{{lng}}/translations.json',
1119
requestOptions: { // used for fetch, can also be a function (payload) => ({ method: 'GET' })

client/jest.setup.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,3 @@ import '@babel/polyfill';
33
// See: https://github.com/testing-library/jest-dom
44
// eslint-disable-next-line import/no-extraneous-dependencies
55
import '@testing-library/jest-dom';
6-
7-
import lodash from 'lodash';
8-
9-
// For testing, we use en-US and provide a mock implementation
10-
// of t() that finds the correct translation
11-
import translations from '../translations/locales/en-US/translations.json';
12-
13-
// This function name needs to be prefixed with "mock" so that Jest doesn't
14-
// complain that it's out-of-scope in the mock below
15-
const mockTranslate = key => lodash.get(translations, key);
16-
17-
jest.mock('react-i18next', () => ({
18-
// this mock makes sure any components using the translate HoC receive the t function as a prop
19-
withTranslation: () => (Component) => {
20-
Component.defaultProps = { ...Component.defaultProps, t: mockTranslate };
21-
return Component;
22-
},
23-
}));

client/modules/IDE/components/AssetList.jsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { bindActionCreators } from 'redux';
55
import { Link } from 'react-router';
66
import { Helmet } from 'react-helmet';
77
import prettyBytes from 'pretty-bytes';
8+
import { withTranslation } from 'react-i18next';
89

910
import Loader from '../../App/components/loader';
1011
import * as AssetActions from '../actions/assets';
@@ -85,7 +86,7 @@ class AssetListRowBase extends React.Component {
8586
onClick={this.toggleOptions}
8687
onBlur={this.onBlurComponent}
8788
onFocus={this.onFocusComponent}
88-
aria-label="Toggle Open/Close Asset Options"
89+
aria-label={this.props.t('AssetList.ToggleOpenCloseARIA')}
8990
>
9091
<DownFilledTriangleIcon focusable="false" aria-hidden="true" />
9192
</button>
@@ -100,7 +101,7 @@ class AssetListRowBase extends React.Component {
100101
onBlur={this.onBlurComponent}
101102
onFocus={this.onFocusComponent}
102103
>
103-
Delete
104+
{this.props.t('AssetList.Delete')}
104105
</button>
105106
</li>
106107
<li>
@@ -111,7 +112,7 @@ class AssetListRowBase extends React.Component {
111112
onFocus={this.onFocusComponent}
112113
className="asset-table__action-option"
113114
>
114-
Open in New Tab
115+
{this.props.t('AssetList.OpenNewTab')}
115116
</Link>
116117
</li>
117118
</ul>}
@@ -131,7 +132,8 @@ AssetListRowBase.propTypes = {
131132
size: PropTypes.number.isRequired
132133
}).isRequired,
133134
deleteAssetRequest: PropTypes.func.isRequired,
134-
username: PropTypes.string.isRequired
135+
username: PropTypes.string.isRequired,
136+
t: PropTypes.func.isRequired
135137
};
136138

137139
function mapStateToPropsAssetListRow(state) {
@@ -153,7 +155,7 @@ class AssetList extends React.Component {
153155
}
154156

155157
getAssetsTitle() {
156-
return 'p5.js Web Editor | My assets';
158+
return this.props.t('AssetList.Title');
157159
}
158160

159161
hasAssets() {
@@ -167,13 +169,13 @@ class AssetList extends React.Component {
167169

168170
renderEmptyTable() {
169171
if (!this.props.loading && this.props.assetList.length === 0) {
170-
return (<p className="asset-table__empty">No uploaded assets.</p>);
172+
return (<p className="asset-table__empty">{this.props.t('AssetList.NoUploadedAssets')}</p>);
171173
}
172174
return null;
173175
}
174176

175177
render() {
176-
const { assetList } = this.props;
178+
const { assetList, t } = this.props;
177179
return (
178180
<article className="asset-table-container">
179181
<Helmet>
@@ -185,9 +187,9 @@ class AssetList extends React.Component {
185187
<table className="asset-table">
186188
<thead>
187189
<tr>
188-
<th>Name</th>
189-
<th>Size</th>
190-
<th>Sketch</th>
190+
<th>{t('AssetList.HeaderName')}</th>
191+
<th>{t('AssetList.HeaderSize')}</th>
192+
<th>{t('AssetList.HeaderSketch')}</th>
191193
<th scope="col"></th>
192194
</tr>
193195
</thead>
@@ -212,7 +214,8 @@ AssetList.propTypes = {
212214
sketchId: PropTypes.string
213215
})).isRequired,
214216
getAssets: PropTypes.func.isRequired,
215-
loading: PropTypes.bool.isRequired
217+
loading: PropTypes.bool.isRequired,
218+
t: PropTypes.func.isRequired
216219
};
217220

218221
function mapStateToProps(state) {
@@ -227,4 +230,4 @@ function mapDispatchToProps(dispatch) {
227230
return bindActionCreators(Object.assign({}, AssetActions), dispatch);
228231
}
229232

230-
export default connect(mapStateToProps, mapDispatchToProps)(AssetList);
233+
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(AssetList));

client/modules/IDE/components/CopyableInput.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
22
import React from 'react';
33
import Clipboard from 'clipboard';
44
import classNames from 'classnames';
5+
import { withTranslation } from 'react-i18next';
56

67
import ShareIcon from '../../../images/share.svg';
78

@@ -45,7 +46,7 @@ class CopyableInput extends React.Component {
4546
<div className={copyableInputClass}>
4647
<div
4748
className="copyable-input__value-container tooltipped-no-delay"
48-
aria-label="Copied to Clipboard!"
49+
aria-label={this.props.t('CopyableInput.CopiedARIA')}
4950
ref={(element) => { this.tooltip = element; }}
5051
onMouseLeave={this.onMouseLeaveHandler}
5152
>
@@ -69,7 +70,7 @@ class CopyableInput extends React.Component {
6970
rel="noopener noreferrer"
7071
href={value}
7172
className="copyable-input__preview"
72-
aria-label={`Open ${label} view in new tab`}
73+
aria-label={this.props.t('CopyableInput.CopiedARIA', { label })}
7374
>
7475
<ShareIcon focusable="false" aria-hidden="true" />
7576
</a>
@@ -82,11 +83,12 @@ class CopyableInput extends React.Component {
8283
CopyableInput.propTypes = {
8384
label: PropTypes.string.isRequired,
8485
value: PropTypes.string.isRequired,
85-
hasPreviewLink: PropTypes.bool
86+
hasPreviewLink: PropTypes.bool,
87+
t: PropTypes.func.isRequired
8688
};
8789

8890
CopyableInput.defaultProps = {
8991
hasPreviewLink: false
9092
};
9193

92-
export default CopyableInput;
94+
export default withTranslation()(CopyableInput);

0 commit comments

Comments
 (0)