Prototype with proposed i18n architecture (#1478)

* Branch with i18n functionality
* Translation files with new entries
* includes Loader in index.jsx
* Uses WithTranslation In Nav
* New Namespace
* Shortcuts Modal Complete
* Preferences complete
* About overlay title translated
This commit is contained in:
ov 2020-07-06 10:36:45 +01:00 committed by GitHub
parent 69bd6cb4a0
commit b05d1b1a02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 550 additions and 158 deletions

View file

@ -4,6 +4,8 @@ import { connect } from 'react-redux';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { Link } from 'react-router'; import { Link } from 'react-router';
import classNames from 'classnames'; import classNames from 'classnames';
import { withTranslation } from 'react-i18next';
import i18next from 'i18next';
import * as IDEActions from '../modules/IDE/actions/ide'; import * as IDEActions from '../modules/IDE/actions/ide';
import * as toastActions from '../modules/IDE/actions/toast'; import * as toastActions from '../modules/IDE/actions/toast';
import * as projectActions from '../modules/IDE/actions/project'; import * as projectActions from '../modules/IDE/actions/project';
@ -55,6 +57,10 @@ class Nav extends React.PureComponent {
this.handleFocusForHelp = this.handleFocus.bind(this, 'help'); this.handleFocusForHelp = this.handleFocus.bind(this, 'help');
this.toggleDropdownForAccount = this.toggleDropdown.bind(this, 'account'); this.toggleDropdownForAccount = this.toggleDropdown.bind(this, 'account');
this.handleFocusForAccount = this.handleFocus.bind(this, 'account'); this.handleFocusForAccount = this.handleFocus.bind(this, 'account');
this.toggleDropdownForLang = this.toggleDropdown.bind(this, 'lang');
this.handleFocusForLang = this.handleFocus.bind(this, 'lang');
this.handleLangSelection = this.handleLangSelection.bind(this);
this.closeDropDown = this.closeDropDown.bind(this); this.closeDropDown = this.closeDropDown.bind(this);
} }
@ -163,6 +169,13 @@ class Nav extends React.PureComponent {
this.setDropdown('none'); this.setDropdown('none');
} }
handleLangSelection(event) {
i18next.changeLanguage(event.target.value);
this.props.showToast(1500);
this.props.setToastText('LangChange');
this.setDropdown('none');
}
handleLogout() { handleLogout() {
this.props.logoutUser(); this.props.logoutUser();
this.setDropdown('none'); this.setDropdown('none');
@ -233,7 +246,7 @@ class Nav extends React.PureComponent {
<Link to="/" className="nav__back-link"> <Link to="/" className="nav__back-link">
<CaretLeftIcon className="nav__back-icon" focusable="false" aria-hidden="true" /> <CaretLeftIcon className="nav__back-icon" focusable="false" aria-hidden="true" />
<span className="nav__item-header"> <span className="nav__item-header">
Back to Editor {this.props.t('BackEditor')}
</span> </span>
</Link> </Link>
</li> </li>
@ -258,7 +271,7 @@ class Nav extends React.PureComponent {
} }
}} }}
> >
<span className="nav__item-header">File</span> <span className="nav__item-header">{this.props.t('File')}</span>
<TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" /> <TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" />
</button> </button>
<ul className="nav__dropdown"> <ul className="nav__dropdown">
@ -268,7 +281,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForFile} onFocus={this.handleFocusForFile}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
New {this.props.t('New')}
</button> </button>
</li> </li>
{ getConfig('LOGIN_ENABLED') && (!this.props.project.owner || this.isUserOwner()) && { getConfig('LOGIN_ENABLED') && (!this.props.project.owner || this.isUserOwner()) &&
@ -278,7 +291,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForFile} onFocus={this.handleFocusForFile}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Save {this.props.t('Save')}
<span className="nav__keyboard-shortcut">{metaKeyName}+S</span> <span className="nav__keyboard-shortcut">{metaKeyName}+S</span>
</button> </button>
</li> } </li> }
@ -289,7 +302,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForFile} onFocus={this.handleFocusForFile}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Duplicate {this.props.t('Duplicate')}
</button> </button>
</li> } </li> }
{ this.props.project.id && { this.props.project.id &&
@ -299,7 +312,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForFile} onFocus={this.handleFocusForFile}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Share {this.props.t('Share')}
</button> </button>
</li> } </li> }
{ this.props.project.id && { this.props.project.id &&
@ -309,7 +322,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForFile} onFocus={this.handleFocusForFile}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Download {this.props.t('Download')}
</button> </button>
</li> } </li> }
{ this.props.user.authenticated && { this.props.user.authenticated &&
@ -320,7 +333,7 @@ class Nav extends React.PureComponent {
onBlur={this.handleBlur} onBlur={this.handleBlur}
onClick={this.setDropdownForNone} onClick={this.setDropdownForNone}
> >
Open {this.props.t('Open')}
</Link> </Link>
</li> } </li> }
{getConfig('UI_COLLECTIONS_ENABLED') && {getConfig('UI_COLLECTIONS_ENABLED') &&
@ -333,7 +346,7 @@ class Nav extends React.PureComponent {
onBlur={this.handleBlur} onBlur={this.handleBlur}
onClick={this.setDropdownForNone} onClick={this.setDropdownForNone}
> >
Add to Collection {this.props.t('AddToCollection')}
</Link> </Link>
</li>} </li>}
{ getConfig('EXAMPLES_ENABLED') && { getConfig('EXAMPLES_ENABLED') &&
@ -344,7 +357,7 @@ class Nav extends React.PureComponent {
onBlur={this.handleBlur} onBlur={this.handleBlur}
onClick={this.setDropdownForNone} onClick={this.setDropdownForNone}
> >
Examples {this.props.t('Examples')}
</Link> </Link>
</li> } </li> }
</ul> </ul>
@ -360,7 +373,7 @@ class Nav extends React.PureComponent {
} }
}} }}
> >
<span className="nav__item-header">Edit</span> <span className="nav__item-header">{this.props.t('Edit')}</span>
<TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" /> <TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" />
</button> </button>
<ul className="nav__dropdown" > <ul className="nav__dropdown" >
@ -373,7 +386,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForEdit} onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Tidy Code {this.props.t('TidyCode')}
<span className="nav__keyboard-shortcut">{'\u21E7'}+Tab</span> <span className="nav__keyboard-shortcut">{'\u21E7'}+Tab</span>
</button> </button>
</li> </li>
@ -383,7 +396,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForEdit} onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Find {this.props.t('Find')}
<span className="nav__keyboard-shortcut">{metaKeyName}+F</span> <span className="nav__keyboard-shortcut">{metaKeyName}+F</span>
</button> </button>
</li> </li>
@ -393,7 +406,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForEdit} onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Find Next {this.props.t('FindNext')}
<span className="nav__keyboard-shortcut">{metaKeyName}+G</span> <span className="nav__keyboard-shortcut">{metaKeyName}+G</span>
</button> </button>
</li> </li>
@ -403,7 +416,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForEdit} onFocus={this.handleFocusForEdit}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Find Previous {this.props.t('FindPrevious')}
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+G</span> <span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+G</span>
</button> </button>
</li> </li>
@ -420,7 +433,7 @@ class Nav extends React.PureComponent {
} }
}} }}
> >
<span className="nav__item-header">Sketch</span> <span className="nav__item-header">{this.props.t('Sketch')}</span>
<TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" /> <TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" />
</button> </button>
<ul className="nav__dropdown"> <ul className="nav__dropdown">
@ -430,7 +443,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForSketch} onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Add File {this.props.t('AddFile')}
</button> </button>
</li> </li>
<li className="nav__dropdown-item"> <li className="nav__dropdown-item">
@ -439,7 +452,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForSketch} onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Add Folder {this.props.t('AddFolder')}
</button> </button>
</li> </li>
<li className="nav__dropdown-item"> <li className="nav__dropdown-item">
@ -448,7 +461,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForSketch} onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Run {this.props.t('Run')}
<span className="nav__keyboard-shortcut">{metaKeyName}+Enter</span> <span className="nav__keyboard-shortcut">{metaKeyName}+Enter</span>
</button> </button>
</li> </li>
@ -458,7 +471,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForSketch} onFocus={this.handleFocusForSketch}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Stop {this.props.t('Stop')}
<span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+Enter</span> <span className="nav__keyboard-shortcut">{'\u21E7'}+{metaKeyName}+Enter</span>
</button> </button>
</li> </li>
@ -495,7 +508,7 @@ class Nav extends React.PureComponent {
} }
}} }}
> >
<span className="nav__item-header">Help</span> <span className="nav__item-header">{this.props.t('Help')}</span>
<TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" /> <TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" />
</button> </button>
<ul className="nav__dropdown"> <ul className="nav__dropdown">
@ -505,7 +518,7 @@ class Nav extends React.PureComponent {
onBlur={this.handleBlur} onBlur={this.handleBlur}
onClick={this.handleKeyboardShortcuts} onClick={this.handleKeyboardShortcuts}
> >
Keyboard Shortcuts {this.props.t('KeyboardShortcuts')}
</button> </button>
</li> </li>
<li className="nav__dropdown-item"> <li className="nav__dropdown-item">
@ -516,7 +529,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForHelp} onFocus={this.handleFocusForHelp}
onBlur={this.handleBlur} onBlur={this.handleBlur}
onClick={this.setDropdownForNone} onClick={this.setDropdownForNone}
>Reference >{this.props.t('Reference')}
</a> </a>
</li> </li>
<li className="nav__dropdown-item"> <li className="nav__dropdown-item">
@ -526,7 +539,7 @@ class Nav extends React.PureComponent {
onBlur={this.handleBlur} onBlur={this.handleBlur}
onClick={this.setDropdownForNone} onClick={this.setDropdownForNone}
> >
About {this.props.t('About')}
</Link> </Link>
</li> </li>
</ul> </ul>
@ -535,18 +548,73 @@ class Nav extends React.PureComponent {
); );
} }
renderLanguageMenu(navDropdownState) {
return (
<ul className="nav__items-right" title="user-menu">
<li className={navDropdownState.lang}>
<button
onClick={this.toggleDropdownForLang}
onBlur={this.handleBlur}
onFocus={this.clearHideTimeout}
onMouseOver={() => {
if (this.state.dropdownOpen !== 'none') {
this.setDropdown('lang');
}
}}
>
<span className="nav__item-header"> {this.props.t('Lang')}</span>
<TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" />
</button>
<ul className="nav__dropdown">
<li className="nav__dropdown-item">
<button
onFocus={this.handleFocusForLang}
onBlur={this.handleBlur}
value="it"
onClick={e => this.handleLangSelection(e)}
>
Italian (Test Fallback)
</button>
</li>
<li className="nav__dropdown-item">
<button
onFocus={this.handleFocusForLang}
onBlur={this.handleBlur}
value="en-US"
onClick={e => this.handleLangSelection(e)}
>English
</button>
</li>
<li className="nav__dropdown-item">
<button
onFocus={this.handleFocusForLang}
onBlur={this.handleBlur}
value="es-419"
onClick={e => this.handleLangSelection(e)}
>
Español
</button>
</li>
</ul>
</li>
</ul>
);
}
renderUnauthenticatedUserMenu(navDropdownState) { renderUnauthenticatedUserMenu(navDropdownState) {
return ( return (
<ul className="nav__items-right" title="user-menu"> <ul className="nav__items-right" title="user-menu">
<li className="nav__item"> <li className="nav__item">
<Link to="/login" className="nav__auth-button"> <Link to="/login" className="nav__auth-button">
<span className="nav__item-header">Log in</span> <span className="nav__item-header">{this.props.t('Login')}</span>
</Link> </Link>
</li> </li>
<span className="nav__item-or">or</span> <span className="nav__item-or">{this.props.t('LoginOr')}</span>
<li className="nav__item"> <li className="nav__item">
<Link to="/signup" className="nav__auth-button"> <Link to="/signup" className="nav__auth-button">
<span className="nav__item-header">Sign up</span> <span className="nav__item-header">{this.props.t('SignUp')}</span>
</Link> </Link>
</li> </li>
</ul> </ul>
@ -557,7 +625,7 @@ class Nav extends React.PureComponent {
return ( return (
<ul className="nav__items-right" title="user-menu"> <ul className="nav__items-right" title="user-menu">
<li className="nav__item"> <li className="nav__item">
<span>Hello, {this.props.user.username}!</span> <span>{this.props.t('Hello')}, {this.props.user.username}!</span>
</li> </li>
<span className="nav__item-spacer">|</span> <span className="nav__item-spacer">|</span>
<li className={navDropdownState.account}> <li className={navDropdownState.account}>
@ -572,7 +640,7 @@ class Nav extends React.PureComponent {
} }
}} }}
> >
My Account {this.props.t('MyAccount')}
<TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" /> <TriangleIcon className="nav__item-header-triangle" focusable="false" aria-hidden="true" />
</button> </button>
<ul className="nav__dropdown"> <ul className="nav__dropdown">
@ -583,7 +651,7 @@ class Nav extends React.PureComponent {
onBlur={this.handleBlur} onBlur={this.handleBlur}
onClick={this.setDropdownForNone} onClick={this.setDropdownForNone}
> >
My sketches {this.props.t('MySketches')}
</Link> </Link>
</li> </li>
{getConfig('UI_COLLECTIONS_ENABLED') && {getConfig('UI_COLLECTIONS_ENABLED') &&
@ -594,7 +662,7 @@ class Nav extends React.PureComponent {
onBlur={this.handleBlur} onBlur={this.handleBlur}
onClick={this.setDropdownForNone} onClick={this.setDropdownForNone}
> >
My collections {this.props.t('MyCollections')}
</Link> </Link>
</li> </li>
} }
@ -605,7 +673,7 @@ class Nav extends React.PureComponent {
onBlur={this.handleBlur} onBlur={this.handleBlur}
onClick={this.setDropdownForNone} onClick={this.setDropdownForNone}
> >
My assets {this.props.t('MyAssets')}
</Link> </Link>
</li> </li>
<li className="nav__dropdown-item"> <li className="nav__dropdown-item">
@ -615,7 +683,7 @@ class Nav extends React.PureComponent {
onBlur={this.handleBlur} onBlur={this.handleBlur}
onClick={this.setDropdownForNone} onClick={this.setDropdownForNone}
> >
Settings {this.props.t('Settings')}
</Link> </Link>
</li> </li>
<li className="nav__dropdown-item"> <li className="nav__dropdown-item">
@ -624,7 +692,7 @@ class Nav extends React.PureComponent {
onFocus={this.handleFocusForAccount} onFocus={this.handleFocusForAccount}
onBlur={this.handleBlur} onBlur={this.handleBlur}
> >
Log out {this.props.t('LogOut')}
</button> </button>
</li> </li>
</ul> </ul>
@ -677,6 +745,10 @@ class Nav extends React.PureComponent {
account: classNames({ account: classNames({
'nav__item': true, 'nav__item': true,
'nav__item--open': this.state.dropdownOpen === 'account' 'nav__item--open': this.state.dropdownOpen === 'account'
}),
lang: classNames({
'nav__item': true,
'nav__item--open': this.state.dropdownOpen === 'lang'
}) })
}; };
@ -684,6 +756,7 @@ class Nav extends React.PureComponent {
<header> <header>
<nav className="nav" title="main-navigation" ref={(node) => { this.node = node; }}> <nav className="nav" title="main-navigation" ref={(node) => { this.node = node; }}>
{this.renderLeftLayout(navDropdownState)} {this.renderLeftLayout(navDropdownState)}
{getConfig('TRANSLATIONS_ENABLED') && this.renderLanguageMenu(navDropdownState)}
{this.renderUserMenu(navDropdownState)} {this.renderUserMenu(navDropdownState)}
</nav> </nav>
</header> </header>
@ -734,7 +807,9 @@ Nav.propTypes = {
}).isRequired, }).isRequired,
params: PropTypes.shape({ params: PropTypes.shape({
username: PropTypes.string username: PropTypes.string
}) }),
t: PropTypes.func.isRequired
}; };
Nav.defaultProps = { Nav.defaultProps = {
@ -767,5 +842,5 @@ const mapDispatchToProps = {
setAllAccessibleOutput setAllAccessibleOutput
}; };
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Nav)); export default withTranslation('WebEditor')(withRouter(connect(mapStateToProps, mapDispatchToProps)(Nav)));
export { Nav as NavComponent }; export { Nav as NavComponent };

View file

@ -44,7 +44,8 @@ describe('Nav', () => {
setToastText: jest.fn(), setToastText: jest.fn(),
rootFile: { rootFile: {
id: 'root-file' id: 'root-file'
} },
t: jest.fn()
}; };
const getWrapper = () => shallow(<NavComponent {...props} />); const getWrapper = () => shallow(<NavComponent {...props} />);

View file

@ -30,9 +30,7 @@ exports[`Nav renders correctly 1`] = `
> >
<span <span
className="nav__item-header" className="nav__item-header"
> />
File
</span>
<test-file-stub <test-file-stub
aria-hidden="true" aria-hidden="true"
className="nav__item-header-triangle" className="nav__item-header-triangle"
@ -49,9 +47,7 @@ exports[`Nav renders correctly 1`] = `
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
> />
New
</button>
</li> </li>
<li <li
className="nav__dropdown-item" className="nav__dropdown-item"
@ -60,9 +56,7 @@ exports[`Nav renders correctly 1`] = `
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
> />
Duplicate
</button>
</li> </li>
<li <li
className="nav__dropdown-item" className="nav__dropdown-item"
@ -71,9 +65,7 @@ exports[`Nav renders correctly 1`] = `
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
> />
Share
</button>
</li> </li>
<li <li
className="nav__dropdown-item" className="nav__dropdown-item"
@ -82,9 +74,7 @@ exports[`Nav renders correctly 1`] = `
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
> />
Download
</button>
</li> </li>
<li <li
className="nav__dropdown-item" className="nav__dropdown-item"
@ -94,9 +84,7 @@ exports[`Nav renders correctly 1`] = `
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
style={Object {}} style={Object {}}
> />
Open
</a>
</li> </li>
</ul> </ul>
</li> </li>
@ -111,9 +99,7 @@ exports[`Nav renders correctly 1`] = `
> >
<span <span
className="nav__item-header" className="nav__item-header"
> />
Edit
</span>
<test-file-stub <test-file-stub
aria-hidden="true" aria-hidden="true"
className="nav__item-header-triangle" className="nav__item-header-triangle"
@ -131,7 +117,6 @@ exports[`Nav renders correctly 1`] = `
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
> >
Tidy Code
<span <span
className="nav__keyboard-shortcut" className="nav__keyboard-shortcut"
> >
@ -148,7 +133,6 @@ exports[`Nav renders correctly 1`] = `
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
> >
Find
<span <span
className="nav__keyboard-shortcut" className="nav__keyboard-shortcut"
> >
@ -165,7 +149,6 @@ exports[`Nav renders correctly 1`] = `
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
> >
Find Next
<span <span
className="nav__keyboard-shortcut" className="nav__keyboard-shortcut"
> >
@ -182,7 +165,6 @@ exports[`Nav renders correctly 1`] = `
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
> >
Find Previous
<span <span
className="nav__keyboard-shortcut" className="nav__keyboard-shortcut"
> >
@ -206,9 +188,7 @@ exports[`Nav renders correctly 1`] = `
> >
<span <span
className="nav__item-header" className="nav__item-header"
> />
Sketch
</span>
<test-file-stub <test-file-stub
aria-hidden="true" aria-hidden="true"
className="nav__item-header-triangle" className="nav__item-header-triangle"
@ -225,9 +205,16 @@ exports[`Nav renders correctly 1`] = `
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
/>
</li>
<li
className="nav__dropdown-item"
> >
Add File <button
</button> onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
/>
</li> </li>
<li <li
className="nav__dropdown-item" className="nav__dropdown-item"
@ -237,18 +224,6 @@ exports[`Nav renders correctly 1`] = `
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
> >
Add Folder
</button>
</li>
<li
className="nav__dropdown-item"
>
<button
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
>
Run
<span <span
className="nav__keyboard-shortcut" className="nav__keyboard-shortcut"
> >
@ -265,7 +240,6 @@ exports[`Nav renders correctly 1`] = `
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
> >
Stop
<span <span
className="nav__keyboard-shortcut" className="nav__keyboard-shortcut"
> >
@ -289,9 +263,7 @@ exports[`Nav renders correctly 1`] = `
> >
<span <span
className="nav__item-header" className="nav__item-header"
> />
Help
</span>
<test-file-stub <test-file-stub
aria-hidden="true" aria-hidden="true"
className="nav__item-header-triangle" className="nav__item-header-triangle"
@ -308,9 +280,7 @@ exports[`Nav renders correctly 1`] = `
onBlur={[Function]} onBlur={[Function]}
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
> />
Keyboard Shortcuts
</button>
</li> </li>
<li <li
className="nav__dropdown-item" className="nav__dropdown-item"
@ -322,9 +292,7 @@ exports[`Nav renders correctly 1`] = `
onFocus={[Function]} onFocus={[Function]}
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> />
Reference
</a>
</li> </li>
<li <li
className="nav__dropdown-item" className="nav__dropdown-item"
@ -334,9 +302,7 @@ exports[`Nav renders correctly 1`] = `
onClick={[Function]} onClick={[Function]}
onFocus={[Function]} onFocus={[Function]}
style={Object {}} style={Object {}}
> />
About
</a>
</li> </li>
</ul> </ul>
</li> </li>

38
client/i18n.js Normal file
View file

@ -0,0 +1,38 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
const fallbackLng = ['en-US'];
const availableLanguages = ['en-US', 'es-419'];
const options = {
loadPath: '/translations/{{lng}}/translations.json',
requestOptions: { // used for fetch, can also be a function (payload) => ({ method: 'GET' })
mode: 'no-cors'
},
allowMultiLoading: false, // set loadPath: '/locales/resources.json?lng={{lng}}&ns={{ns}}' to adapt to multiLoading
};
i18n
.use(initReactI18next) // pass the i18n instance to react-i18next.
.use(LanguageDetector)// to detect the language from currentBrowser
.use(Backend) // to fetch the data from server
.init({
lng: 'en-US',
defaultNS: 'WebEditor',
fallbackLng, // if user computer language is not on the list of available languages, than we will be using the fallback language specified earlier
debug: false,
backend: options,
getAsync: false,
initImmediate: false,
useSuspense: true,
whitelist: availableLanguages,
interpolation: {
escapeValue: false, // react already safes from xss
},
saveMissing: false, // if a key is not found AND this flag is set to true, i18next will call the handler missingKeyHandler
missingKeyHandler: false // function(lng, ns, key, fallbackValue) { } custom logic about how to handle the missing keys
});
export default i18n;

View file

@ -1,4 +1,4 @@
import React from 'react'; import React, { Suspense } from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import { hot } from 'react-hot-loader/root'; import { hot } from 'react-hot-loader/root';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
@ -7,6 +7,8 @@ import { Router, browserHistory } from 'react-router';
import configureStore from './store'; import configureStore from './store';
import routes from './routes'; import routes from './routes';
import ThemeProvider from './modules/App/components/ThemeProvider'; import ThemeProvider from './modules/App/components/ThemeProvider';
import Loader from './modules/App/components/loader';
import i18n from './i18n';
require('./styles/main.scss'); require('./styles/main.scss');
@ -29,6 +31,8 @@ const App = () => (
const HotApp = hot(App); const HotApp = hot(App);
render( render(
<HotApp />, <Suspense fallback={(<Loader />)}>
<HotApp />
</Suspense>,
document.getElementById('root') document.getElementById('root')
); );

View file

@ -1,15 +1,16 @@
import React from 'react'; import React from 'react';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import SquareLogoIcon from '../../../images/p5js-square-logo.svg'; import SquareLogoIcon from '../../../images/p5js-square-logo.svg';
// import PlayIcon from '../../../images/play.svg'; // import PlayIcon from '../../../images/play.svg';
import AsteriskIcon from '../../../images/p5-asterisk.svg'; import AsteriskIcon from '../../../images/p5-asterisk.svg';
function About(props) { function About(props) {
const { t } = useTranslation();
return ( return (
<div className="about__content"> <div className="about__content">
<Helmet> <Helmet>
<title>p5.js Web Editor | About</title> <title>p5.js Web Editor | About </title>
</Helmet> </Helmet>
<div className="about__content-column"> <div className="about__content-column">
<SquareLogoIcon className="about__logo" role="img" aria-label="p5.js Logo" focusable="false" /> <SquareLogoIcon className="about__logo" role="img" aria-label="p5.js Logo" focusable="false" />
@ -25,7 +26,7 @@ function About(props) {
</p> */} </p> */}
</div> </div>
<div className="about__content-column"> <div className="about__content-column">
<h3 className="about__content-column-title">New to p5.js?</h3> <h3 className="about__content-column-title">{t('NewP5')}</h3>
<p className="about__content-column-list"> <p className="about__content-column-list">
<a <a
href="https://p5js.org/examples/" href="https://p5js.org/examples/"
@ -33,7 +34,7 @@ function About(props) {
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<AsteriskIcon className="about__content-column-asterisk" aria-hidden="true" focusable="false" /> <AsteriskIcon className="about__content-column-asterisk" aria-hidden="true" focusable="false" />
Examples {t('Examples')}
</a> </a>
</p> </p>
<p className="about__content-column-list"> <p className="about__content-column-list">
@ -43,12 +44,12 @@ function About(props) {
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<AsteriskIcon className="about__content-column-asterisk" aria-hidden="true" focusable="false" /> <AsteriskIcon className="about__content-column-asterisk" aria-hidden="true" focusable="false" />
Learn {t('Learn')}
</a> </a>
</p> </p>
</div> </div>
<div className="about__content-column"> <div className="about__content-column">
<h3 className="about__content-column-title">Resources</h3> <h3 className="about__content-column-title">{t('Resources')}</h3>
<p className="about__content-column-list"> <p className="about__content-column-list">
<a <a
href="https://p5js.org/libraries/" href="https://p5js.org/libraries/"
@ -56,7 +57,7 @@ function About(props) {
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<AsteriskIcon className="about__content-column-asterisk" aria-hidden="true" focusable="false" /> <AsteriskIcon className="about__content-column-asterisk" aria-hidden="true" focusable="false" />
Libraries {t('Libraries')}
</a> </a>
</p> </p>
<p className="about__content-column-list"> <p className="about__content-column-list">
@ -66,7 +67,7 @@ function About(props) {
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<AsteriskIcon className="about__content-column-asterisk" aria-hidden="true" focusable="false" /> <AsteriskIcon className="about__content-column-asterisk" aria-hidden="true" focusable="false" />
Reference {t('Reference')}
</a> </a>
</p> </p>
<p className="about__content-column-list"> <p className="about__content-column-list">
@ -76,7 +77,7 @@ function About(props) {
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<AsteriskIcon className="about__content-column-asterisk" aria-hidden="true" focusable="false" /> <AsteriskIcon className="about__content-column-asterisk" aria-hidden="true" focusable="false" />
Forum {t('Forum')}
</a> </a>
</p> </p>
</div> </div>
@ -86,7 +87,7 @@ function About(props) {
href="https://github.com/processing/p5.js-web-editor" href="https://github.com/processing/p5.js-web-editor"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
>Contribute >{t('Contribute')}
</a> </a>
</p> </p>
<p className="about__footer-list"> <p className="about__footer-list">
@ -94,7 +95,7 @@ function About(props) {
href="https://github.com/processing/p5.js-web-editor/issues/new" href="https://github.com/processing/p5.js-web-editor/issues/new"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
>Report a bug >{t('Report')}
</a> </a>
</p> </p>
<p className="about__footer-list"> <p className="about__footer-list">

View file

@ -1,53 +1,55 @@
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next';
import { metaKeyName, } from '../../../utils/metaKey'; import { metaKeyName, } from '../../../utils/metaKey';
function KeyboardShortcutModal() { function KeyboardShortcutModal() {
const { t } = useTranslation();
return ( return (
<div className="keyboard-shortcuts"> <div className="keyboard-shortcuts">
<h3 className="keyboard-shortcuts__title">Code Editing</h3> <h3 className="keyboard-shortcuts__title">{t('CodeEditing')}</h3>
<p className="keyboard-shortcuts__description"> <p className="keyboard-shortcuts__description">
Code editing keyboard shortcuts follow <a href="https://shortcuts.design/toolspage-sublimetext.html" target="_blank" rel="noopener noreferrer">Sublime Text shortcuts</a>. {t('Code editing keyboard shortcuts follow')} <a href="https://shortcuts.design/toolspage-sublimetext.html" target="_blank" rel="noopener noreferrer">{t('Sublime Text shortcuts')}</a>.
</p> </p>
<ul className="keyboard-shortcuts__list"> <ul className="keyboard-shortcuts__list">
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command">{'\u21E7'} + Tab</span> <span className="keyboard-shortcut__command">{'\u21E7'} + Tab</span>
<span>Tidy</span> <span>{t('Tidy')}</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + F {metaKeyName} + F
</span> </span>
<span>Find Text</span> <span>{t('FindText')}</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + G {metaKeyName} + G
</span> </span>
<span>Find Next Text Match</span> <span>{t('FindNextTextMatch')}</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + {'\u21E7'} + G {metaKeyName} + {'\u21E7'} + G
</span> </span>
<span>Find Previous Text Match</span> <span>{t('FindPreviousTextMatch')}</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + [ {metaKeyName} + [
</span> </span>
<span>Indent Code Left</span> <span>{t('IndentCodeLeft')}</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + ] {metaKeyName} + ]
</span> </span>
<span>Indent Code Right</span> <span>{t('IndentCodeRight')}</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + / {metaKeyName} + /
</span> </span>
<span>Comment Line</span> <span>{t('CommentLine')}</span>
</li> </li>
</ul> </ul>
<h3 className="keyboard-shortcuts__title">General</h3> <h3 className="keyboard-shortcuts__title">General</h3>
@ -56,31 +58,31 @@ function KeyboardShortcutModal() {
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + S {metaKeyName} + S
</span> </span>
<span>Save</span> <span>{t('Save')}</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + Enter {metaKeyName} + Enter
</span> </span>
<span>Start Sketch</span> <span>{t('StartSketch')}</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + {'\u21E7'} + Enter {metaKeyName} + {'\u21E7'} + Enter
</span> </span>
<span>Stop Sketch</span> <span>{t('StopSketch')}</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + {'\u21E7'} + 1 {metaKeyName} + {'\u21E7'} + 1
</span> </span>
<span>Turn on Accessible Output</span> <span>{t('TurnOnAccessibleOutput')}</span>
</li> </li>
<li className="keyboard-shortcut-item"> <li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command"> <span className="keyboard-shortcut__command">
{metaKeyName} + {'\u21E7'} + 2 {metaKeyName} + {'\u21E7'} + 2
</span> </span>
<span>Turn off Accessible Output</span> <span>{t('TurnOffAccessibleOutput')}</span>
</li> </li>
</ul> </ul>
</div> </div>

View file

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import { withTranslation } from 'react-i18next';
// import { bindActionCreators } from 'redux'; // import { bindActionCreators } from 'redux';
// import { connect } from 'react-redux'; // import { connect } from 'react-redux';
// import * as PreferencesActions from '../actions/preferences'; // import * as PreferencesActions from '../actions/preferences';
@ -98,13 +99,13 @@ class Preferences extends React.Component {
<Tabs> <Tabs>
<TabList> <TabList>
<div className="tabs__titles"> <div className="tabs__titles">
<Tab><h4 className="tabs__title">General Settings</h4></Tab> <Tab><h4 className="tabs__title">{this.props.t('GeneralSettings')}</h4></Tab>
<Tab><h4 className="tabs__title">Accessibility</h4></Tab> <Tab><h4 className="tabs__title">{this.props.t('Accessibility')}</h4></Tab>
</div> </div>
</TabList> </TabList>
<TabPanel> <TabPanel>
<div className="preference"> <div className="preference">
<h4 className="preference__title">Theme</h4> <h4 className="preference__title">{this.props.t('Theme')}</h4>
<div className="preference__options"> <div className="preference__options">
<input <input
type="radio" type="radio"
@ -116,7 +117,7 @@ class Preferences extends React.Component {
value="light" value="light"
checked={this.props.theme === 'light'} checked={this.props.theme === 'light'}
/> />
<label htmlFor="light-theme-on" className="preference__option">Light</label> <label htmlFor="light-theme-on" className="preference__option">{this.props.t('Light')}</label>
<input <input
type="radio" type="radio"
onChange={() => this.props.setTheme('dark')} onChange={() => this.props.setTheme('dark')}
@ -127,7 +128,7 @@ class Preferences extends React.Component {
value="dark" value="dark"
checked={this.props.theme === 'dark'} checked={this.props.theme === 'dark'}
/> />
<label htmlFor="dark-theme-on" className="preference__option">Dark</label> <label htmlFor="dark-theme-on" className="preference__option">{this.props.t('Dark')}</label>
<input <input
type="radio" type="radio"
onChange={() => this.props.setTheme('contrast')} onChange={() => this.props.setTheme('contrast')}
@ -138,11 +139,11 @@ class Preferences extends React.Component {
value="contrast" value="contrast"
checked={this.props.theme === 'contrast'} checked={this.props.theme === 'contrast'}
/> />
<label htmlFor="high-contrast-theme-on" className="preference__option">High Contrast</label> <label htmlFor="high-contrast-theme-on" className="preference__option">{this.props.t('HighContrast')}</label>
</div> </div>
</div> </div>
<div className="preference"> <div className="preference">
<h4 className="preference__title">Text size</h4> <h4 className="preference__title">{this.props.t('TextSize')}</h4>
<button <button
className="preference__minus-button" className="preference__minus-button"
onClick={this.decreaseFontSize} onClick={this.decreaseFontSize}
@ -150,7 +151,7 @@ class Preferences extends React.Component {
disabled={this.state.fontSize <= 8} disabled={this.state.fontSize <= 8}
> >
<MinusIcon focusable="false" aria-hidden="true" /> <MinusIcon focusable="false" aria-hidden="true" />
<h6 className="preference__label">Decrease</h6> <h6 className="preference__label">{this.props.t('Decrease')}</h6>
</button> </button>
<form onSubmit={this.onFontInputSubmit}> <form onSubmit={this.onFontInputSubmit}>
<input <input
@ -171,11 +172,11 @@ class Preferences extends React.Component {
disabled={this.state.fontSize >= 36} disabled={this.state.fontSize >= 36}
> >
<PlusIcon focusable="false" aria-hidden="true" /> <PlusIcon focusable="false" aria-hidden="true" />
<h6 className="preference__label">Increase</h6> <h6 className="preference__label">{this.props.t('Increase')}</h6>
</button> </button>
</div> </div>
<div className="preference"> <div className="preference">
<h4 className="preference__title">Autosave</h4> <h4 className="preference__title">{this.props.t('Autosave')}</h4>
<div className="preference__options"> <div className="preference__options">
<input <input
type="radio" type="radio"
@ -187,7 +188,7 @@ class Preferences extends React.Component {
value="On" value="On"
checked={this.props.autosave} checked={this.props.autosave}
/> />
<label htmlFor="autosave-on" className="preference__option">On</label> <label htmlFor="autosave-on" className="preference__option">{this.props.t('On')}</label>
<input <input
type="radio" type="radio"
onChange={() => this.props.setAutosave(false)} onChange={() => this.props.setAutosave(false)}
@ -198,11 +199,11 @@ class Preferences extends React.Component {
value="Off" value="Off"
checked={!this.props.autosave} checked={!this.props.autosave}
/> />
<label htmlFor="autosave-off" className="preference__option">Off</label> <label htmlFor="autosave-off" className="preference__option">{this.props.t('Off')}</label>
</div> </div>
</div> </div>
<div className="preference"> <div className="preference">
<h4 className="preference__title">Word Wrap</h4> <h4 className="preference__title">{this.props.t('WordWrap')}</h4>
<div className="preference__options"> <div className="preference__options">
<input <input
type="radio" type="radio"
@ -214,7 +215,7 @@ class Preferences extends React.Component {
value="On" value="On"
checked={this.props.linewrap} checked={this.props.linewrap}
/> />
<label htmlFor="linewrap-on" className="preference__option">On</label> <label htmlFor="linewrap-on" className="preference__option">{this.props.t('On')}</label>
<input <input
type="radio" type="radio"
onChange={() => this.props.setLinewrap(false)} onChange={() => this.props.setLinewrap(false)}
@ -225,13 +226,13 @@ class Preferences extends React.Component {
value="Off" value="Off"
checked={!this.props.linewrap} checked={!this.props.linewrap}
/> />
<label htmlFor="linewrap-off" className="preference__option">Off</label> <label htmlFor="linewrap-off" className="preference__option">{this.props.t('Off')}</label>
</div> </div>
</div> </div>
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<div className="preference"> <div className="preference">
<h4 className="preference__title">Line numbers</h4> <h4 className="preference__title">{this.props.t('LineNumbers')}</h4>
<div className="preference__options"> <div className="preference__options">
<input <input
type="radio" type="radio"
@ -243,7 +244,7 @@ class Preferences extends React.Component {
value="On" value="On"
checked={this.props.lineNumbers} checked={this.props.lineNumbers}
/> />
<label htmlFor="line-numbers-on" className="preference__option">On</label> <label htmlFor="line-numbers-on" className="preference__option">{this.props.t('On')}</label>
<input <input
type="radio" type="radio"
onChange={() => this.props.setLineNumbers(false)} onChange={() => this.props.setLineNumbers(false)}
@ -254,11 +255,11 @@ class Preferences extends React.Component {
value="Off" value="Off"
checked={!this.props.lineNumbers} checked={!this.props.lineNumbers}
/> />
<label htmlFor="line-numbers-off" className="preference__option">Off</label> <label htmlFor="line-numbers-off" className="preference__option">{this.props.t('Off')}</label>
</div> </div>
</div> </div>
<div className="preference"> <div className="preference">
<h4 className="preference__title">Lint warning sound</h4> <h4 className="preference__title">{this.props.t('LintWarningSound')}</h4>
<div className="preference__options"> <div className="preference__options">
<input <input
type="radio" type="radio"
@ -270,7 +271,7 @@ class Preferences extends React.Component {
value="On" value="On"
checked={this.props.lintWarning} checked={this.props.lintWarning}
/> />
<label htmlFor="lint-warning-on" className="preference__option">On</label> <label htmlFor="lint-warning-on" className="preference__option">{this.props.t('On')}</label>
<input <input
type="radio" type="radio"
onChange={() => this.props.setLintWarning(false)} onChange={() => this.props.setLintWarning(false)}
@ -281,19 +282,19 @@ class Preferences extends React.Component {
value="Off" value="Off"
checked={!this.props.lintWarning} checked={!this.props.lintWarning}
/> />
<label htmlFor="lint-warning-off" className="preference__option">Off</label> <label htmlFor="lint-warning-off" className="preference__option">{this.props.t('Off')}</label>
<button <button
className="preference__preview-button" className="preference__preview-button"
onClick={() => beep.play()} onClick={() => beep.play()}
aria-label="preview sound" aria-label="preview sound"
> >
Preview sound {this.props.t('PreviewSound')}
</button> </button>
</div> </div>
</div> </div>
<div className="preference"> <div className="preference">
<h4 className="preference__title">Accessible text-based canvas</h4> <h4 className="preference__title">{this.props.t('AccessibleTextBasedCanvas')}</h4>
<h6 className="preference__subtitle">Used with screen reader</h6> <h6 className="preference__subtitle">{this.props.t('UsedScreenReader')}</h6>
<div className="preference__options"> <div className="preference__options">
<input <input
@ -307,7 +308,7 @@ class Preferences extends React.Component {
value="On" value="On"
checked={(this.props.textOutput)} checked={(this.props.textOutput)}
/> />
<label htmlFor="text-output-on" className="preference__option preference__canvas">Plain-text</label> <label htmlFor="text-output-on" className="preference__option preference__canvas">{this.props.t('PlainText')}</label>
<input <input
type="checkbox" type="checkbox"
onChange={(event) => { onChange={(event) => {
@ -319,7 +320,7 @@ class Preferences extends React.Component {
value="On" value="On"
checked={(this.props.gridOutput)} checked={(this.props.gridOutput)}
/> />
<label htmlFor="table-output-on" className="preference__option preference__canvas">Table-text</label> <label htmlFor="table-output-on" className="preference__option preference__canvas">{this.props.t('TableText')}</label>
<input <input
type="checkbox" type="checkbox"
onChange={(event) => { onChange={(event) => {
@ -331,7 +332,7 @@ class Preferences extends React.Component {
value="On" value="On"
checked={(this.props.soundOutput)} checked={(this.props.soundOutput)}
/> />
<label htmlFor="sound-output-on" className="preference__option preference__canvas">Sound</label> <label htmlFor="sound-output-on" className="preference__option preference__canvas">{this.props.t('Sound')}</label>
</div> </div>
</div> </div>
</TabPanel> </TabPanel>
@ -360,6 +361,7 @@ Preferences.propTypes = {
setLintWarning: PropTypes.func.isRequired, setLintWarning: PropTypes.func.isRequired,
theme: PropTypes.string.isRequired, theme: PropTypes.string.isRequired,
setTheme: PropTypes.func.isRequired, setTheme: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
}; };
export default Preferences; export default withTranslation('WebEditor')(Preferences);

View file

@ -2,15 +2,17 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { useTranslation } from 'react-i18next';
import * as ToastActions from '../actions/toast'; import * as ToastActions from '../actions/toast';
import ExitIcon from '../../../images/exit.svg'; import ExitIcon from '../../../images/exit.svg';
function Toast(props) { function Toast(props) {
const { t } = useTranslation('WebEditor');
return ( return (
<section className="toast"> <section className="toast">
<p> <p>
{props.text} {t(props.text)}
</p> </p>
<button className="toast__close" onClick={props.hideToast} aria-label="Close Alert" > <button className="toast__close" onClick={props.hideToast} aria-label="Close Alert" >
<ExitIcon focusable="false" aria-hidden="true" /> <ExitIcon focusable="false" aria-hidden="true" />

View file

@ -3,6 +3,7 @@ import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { withTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import SplitPane from 'react-split-pane'; import SplitPane from 'react-split-pane';
import Editor from '../components/Editor'; import Editor from '../components/Editor';
@ -196,7 +197,7 @@ class IDEView extends React.Component {
this.props.persistState(); this.props.persistState();
window.onbeforeunload = null; window.onbeforeunload = null;
} else if (this.props.ide.unsavedChanges) { } else if (this.props.ide.unsavedChanges) {
if (!window.confirm('Are you sure you want to leave this page? You have unsaved changes.')) { if (!window.confirm(this.props.t('WarningUnsavedChanges'))) {
return false; return false;
} }
this.props.setUnsavedChanges(false); this.props.setUnsavedChanges(false);
@ -218,7 +219,7 @@ class IDEView extends React.Component {
<Toolbar /> <Toolbar />
{this.props.ide.preferencesIsVisible && {this.props.ide.preferencesIsVisible &&
<Overlay <Overlay
title="Settings" title={this.props.t('Settings')}
ariaLabel="settings" ariaLabel="settings"
closeOverlay={this.props.closePreferences} closeOverlay={this.props.closePreferences}
> >
@ -334,7 +335,7 @@ class IDEView extends React.Component {
</SplitPane> </SplitPane>
<section className="preview-frame-holder"> <section className="preview-frame-holder">
<header className="preview-frame__header"> <header className="preview-frame__header">
<h2 className="preview-frame__title">Preview</h2> <h2 className="preview-frame__title">{this.props.t('Preview')}</h2>
</header> </header>
<div className="preview-frame__content"> <div className="preview-frame__content">
<div className="preview-frame-overlay" ref={(element) => { this.overlay = element; }}> <div className="preview-frame-overlay" ref={(element) => { this.overlay = element; }}>
@ -395,7 +396,7 @@ class IDEView extends React.Component {
} }
{ this.props.location.pathname === '/about' && { this.props.location.pathname === '/about' &&
<Overlay <Overlay
title="About" title={this.props.t('About')}
previousPath={this.props.ide.previousPath} previousPath={this.props.ide.previousPath}
ariaLabel="about" ariaLabel="about"
> >
@ -441,7 +442,7 @@ class IDEView extends React.Component {
} }
{this.props.ide.keyboardShortcutVisible && {this.props.ide.keyboardShortcutVisible &&
<Overlay <Overlay
title="Keyboard Shortcuts" title={this.props.t('KeyboardShortcuts')}
ariaLabel="keyboard shortcuts" ariaLabel="keyboard shortcuts"
closeOverlay={this.props.closeKeyboardShortcutModal} closeOverlay={this.props.closeKeyboardShortcutModal}
> >
@ -609,7 +610,8 @@ IDEView.propTypes = {
hideRuntimeErrorWarning: PropTypes.func.isRequired, hideRuntimeErrorWarning: PropTypes.func.isRequired,
startSketch: PropTypes.func.isRequired, startSketch: PropTypes.func.isRequired,
openUploadFileModal: PropTypes.func.isRequired, openUploadFileModal: PropTypes.func.isRequired,
closeUploadFileModal: PropTypes.func.isRequired closeUploadFileModal: PropTypes.func.isRequired,
t: PropTypes.func.isRequired
}; };
function mapStateToProps(state) { function mapStateToProps(state) {
@ -646,4 +648,6 @@ function mapDispatchToProps(dispatch) {
); );
} }
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(IDEView));
export default withTranslation('WebEditor')(withRouter(connect(mapStateToProps, mapDispatchToProps)(IDEView)));

66
package-lock.json generated
View file

@ -16272,6 +16272,14 @@
} }
} }
}, },
"html-parse-stringify2": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz",
"integrity": "sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=",
"requires": {
"void-elements": "^2.0.1"
}
},
"html-tags": { "html-tags": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz",
@ -16692,6 +16700,52 @@
} }
} }
}, },
"i18next": {
"version": "19.5.1",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-19.5.1.tgz",
"integrity": "sha512-e6lGMiTyb51F9PPInUTTDg8YbwcWXZYX18svaX2NUUWEphJKP7oG5HMlbZ+K84AXqw4AeBnwRrOlS9ickqcCBg==",
"requires": {
"@babel/runtime": "^7.10.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.10.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz",
"integrity": "sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"regenerator-runtime": {
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
}
}
},
"i18next-browser-languagedetector": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.3.1.tgz",
"integrity": "sha512-KIToAzf8zwWvacgnRwJp63ase26o24AuNUlfNVJ5YZAFmdGhsJpmFClxXPuk9rv1FMI4lnc8zLSqgZPEZMrW4g==",
"requires": {
"@babel/runtime": "^7.5.5"
}
},
"i18next-http-backend": {
"version": "1.0.15",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.0.15.tgz",
"integrity": "sha512-AOGNcB47n0S4GANyVhGUeLRzUUgvh6Lf5vNs/+G3cCP2Mri0OseO2rX0VLHE6PcL21mswW7ka1nmjGKviXKnjQ==",
"requires": {
"node-fetch": "2.6.0"
},
"dependencies": {
"node-fetch": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
}
}
},
"iconv-lite": { "iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -27010,6 +27064,15 @@
"prop-types": "^15.6.1" "prop-types": "^15.6.1"
} }
}, },
"react-i18next": {
"version": "11.7.0",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.7.0.tgz",
"integrity": "sha512-8tvVkpuxQlubcszZON+jmoCgiA9gCZ74OAYli9KChPhETtq8pJsANBTe9KRLRLmX3ubumgvidURWr0VvKz1tww==",
"requires": {
"@babel/runtime": "^7.3.1",
"html-parse-stringify2": "2.0.1"
}
},
"react-input-autosize": { "react-input-autosize": {
"version": "2.2.2", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz",
@ -32181,8 +32244,7 @@
"void-elements": { "void-elements": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
"integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
"dev": true
}, },
"vue-docgen-api": { "vue-docgen-api": {
"version": "4.20.0", "version": "4.20.0",

View file

@ -151,6 +151,9 @@
"express-session": "^1.17.0", "express-session": "^1.17.0",
"friendly-words": "^1.1.10", "friendly-words": "^1.1.10",
"htmlhint": "^0.10.1", "htmlhint": "^0.10.1",
"i18next": "^19.4.5",
"i18next-browser-languagedetector": "^4.2.0",
"i18next-http-backend": "^1.0.15",
"is-url": "^1.2.4", "is-url": "^1.2.4",
"jest-express": "^1.11.0", "jest-express": "^1.11.0",
"js-beautify": "^1.10.3", "js-beautify": "^1.10.3",
@ -178,6 +181,7 @@
"react-dom": "^16.12.0", "react-dom": "^16.12.0",
"react-helmet": "^5.1.3", "react-helmet": "^5.1.3",
"react-hot-loader": "^4.12.19", "react-hot-loader": "^4.12.19",
"react-i18next": "^11.5.0",
"react-redux": "^5.1.2", "react-redux": "^5.1.2",
"react-router": "^3.2.5", "react-router": "^3.2.5",
"react-split-pane": "^0.1.89", "react-split-pane": "^0.1.89",

View file

@ -79,6 +79,7 @@ app.options('*', corsMiddleware);
app.use(Express.static(path.resolve(__dirname, '../dist/static'), { app.use(Express.static(path.resolve(__dirname, '../dist/static'), {
maxAge: process.env.STATIC_MAX_AGE || (process.env.NODE_ENV === 'production' ? '1d' : '0') maxAge: process.env.STATIC_MAX_AGE || (process.env.NODE_ENV === 'production' ? '1d' : '0')
})); }));
app.use('/translations', Express.static('translations/locales/'));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true })); app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
app.use(bodyParser.json({ limit: '50mb' })); app.use(bodyParser.json({ limit: '50mb' }));
app.use(cookieParser()); app.use(cookieParser());

View file

@ -33,6 +33,8 @@ export function renderIndex() {
window.process.env.UI_COLLECTIONS_ENABLED = ${process.env.UI_COLLECTIONS_ENABLED === 'false' ? false : true}; window.process.env.UI_COLLECTIONS_ENABLED = ${process.env.UI_COLLECTIONS_ENABLED === 'false' ? false : true};
window.process.env.UPLOAD_LIMIT = ${process.env.UPLOAD_LIMIT ? `${process.env.UPLOAD_LIMIT}` : undefined}; window.process.env.UPLOAD_LIMIT = ${process.env.UPLOAD_LIMIT ? `${process.env.UPLOAD_LIMIT}` : undefined};
window.process.env.MOBILE_ENABLED = ${process.env.MOBILE_ENABLED ? `${process.env.MOBILE_ENABLED}` : undefined}; window.process.env.MOBILE_ENABLED = ${process.env.MOBILE_ENABLED ? `${process.env.MOBILE_ENABLED}` : undefined};
window.process.env.TRANSLATIONS_ENABLED = ${process.env.TRANSLATIONS_ENABLED === 'true' ? true :false};
</script> </script>
</head> </head>
<body> <body>

View file

@ -0,0 +1,115 @@
{
"Contribute": "Contribute",
"NewP5": "New to p5.js?",
"Report": "Report a bug",
"Learn": "Learn",
"About": "About",
"Resources": "Resources",
"Libraries": "Libraries",
"Forum": "Forum",
"File": "File",
"New": "New",
"Save": "Save",
"Share": "Share",
"Duplicate": "Duplicate",
"Examples": "Examples",
"Edit": "Edit",
"TidyCode": "Tidy Code",
"Find": "Find",
"AddToCollection": "Add to Collection",
"FindNext": "Find Next",
"FindPrevious": "Find Previous",
"Sketch": "Sketch",
"AddFile": "Add File",
"AddFolder": "Add Folder",
"Run": "Run",
"Stop": "Stop",
"Help": "Help",
"KeyboardShortcuts": "Keyboard Shortcuts",
"Reference": "Reference",
"Tidy": "Tidy",
"Lang": "Language",
"FindNextMatch": "Find Next Match",
"FindPrevMatch": "Find Previous Match",
"IndentCodeLeft": "Indent Code Left",
"IndentCodeRight": "Indent Code Right",
"CommentLine": "Comment Line",
"StartSketch": "Start Sketch",
"StopSketch": "StopSketch",
"TurnOnAccessibleOutput": "Turn On Accessible Output",
"TurnOffAccessibleOutput": "Turn Off Accessible Output",
"ToogleSidebar": "Toogle Sidebar",
"ToogleConsole": "Toogle Console",
"Preview": "Preview",
"Auto-refresh": "Auto-refresh",
"Console": "Console",
"Settings": "Settings",
"GeneralSettings": "General settings",
"Theme": "Theme",
"Light": "Light",
"Dark": "Dark",
"HighContrast": "High Contrast",
"TextSize": "Text Size",
"Decrease": "Decrease",
"Increase": "Increase",
"IndentationAmount": "Indentation amount",
"Autosave": "Autosave",
"On": "On",
"Off": "Off",
"SketchSettings": "Sketch Settings",
"SecurityProtocol": "Security Protocol",
"ServeOverHTTPS": "Serve over HTTPS",
"Accessibility": "Accessibility",
"LintWarningSound": "Lint warning sound",
"PreviewSound": "Preview sound",
"AccessibleTextBasedCanvas": "Accessible text-based canvas",
"UsedScreenReader": "Used with screen reader",
"PlainText": "Plain-text",
"TableText": "Table-text",
"Sound": "Sound",
"WordWrap": "Word Wrap",
"LineNumbers": "Line numbers",
"LangChange": "Language changed",
"Welcome": "Welcome",
"Login": "Log in",
"LoginOr": "or",
"SignUp": "Sign up",
"Email": "email",
"Username": "username",
"LoginGithub": "Login with Github",
"LoginGoogle": "Login with Google",
"DontHaveAccount": "Don't have an account?",
"ForgotPassword": "Forgot your password?",
"ResetPassword": "Reset your password",
"BackEditor": "Back to Editor",
"UsernameSplit": "User Name",
"Password": "Password",
"ConfirmPassword": "Confirm Password",
"OpenedNewSketch": "Opened new sketch.",
"Hello": "Hello",
"MyAccount": "My Account",
"My":"My",
"Sketches": "My sketches",
"Collections": "My collections",
"Asset": "Asset",
"MyAssets": "My assets",
"TitleAbout": "p5.js Web Editor | About",
"CodeEditing": "Code Editing",
"Error": "Error",
"In order to save": "In order to save",
"you must be logged in": "you must be logged in",
"Please": "please",
"Find in files": "Find in files",
"Create": "Create",
"enter a name": "enter a name",
"Add": "Add",
"Folder": "Folder",
"FindText": "Find Text",
"FindNextTextMatch": "Find Next Text Match",
"FindPreviousTextMatch": "Find Previous Text Match",
"Code editing keyboard shortcuts follow": "Code editing keyboard shortcuts follow",
"Sublime Text shortcuts": "Sublime Text shortcuts",
"WarningUnsavedChanges": "Are you sure you want to leave this page? You have unsaved changes."
}

View file

@ -0,0 +1,113 @@
{
"Contribute": "Contribuir",
"NewP5": "¿Empezando con p5.js?",
"Report": "Reporta un error",
"Learn": "Aprende",
"About": "Acerca de",
"Resources": "Recursos",
"Libraries": "Bibliotecas",
"Forum": "Foro",
"File": "Archivo",
"New": "Nuevo",
"Save": "Guardar",
"Share": "Compartir",
"Duplicate": "Duplicar",
"Examples": "Ejemplos",
"Edit": "Editar",
"TidyCode": "Ordenar código",
"Find": "Buscar",
"AddToCollection": "Agregar a colección",
"FindNext": "Buscar siguiente",
"FindPrevious": "Buscar anterior",
"Sketch": "Bosquejo",
"AddFile": "Agregar archivo",
"AddFolder": "Agregar directorio",
"Run": "Ejecutar",
"Stop": "Detener",
"Help": "Ayuda",
"KeyboardShortcuts": "Atajos",
"Reference": "Referencia",
"Tidy": "Ordenar",
"Lang": "Lenguaje",
"FindNextMatch": "Encontrar siguiente ocurrencia",
"FindPrevMatch": "Encontrar ocurrencia previa",
"IndentCodeLeft": "Indentar código a la izquierda",
"IndentCodeRight": "Indentar código a la derecha",
"CommentLine": "Comentar línea de código",
"StartSketch": "Iniciar bosquejo",
"StopSketch": "Detener bosquejo",
"TurnOnAccessibleOutput": "Activar salida accesible",
"TurnOffAccessibleOutput": "Desactivar salida accesible",
"ToogleSidebar": "Alternar barra de deslizamiento",
"ToogleConsole": "Alternar consola",
"Preview": "Vista previa",
"Auto-refresh": "Auto-refrescar",
"Console": "Consola",
"Settings": "Configuración",
"GeneralSettings": "Configuración general",
"Theme": "Modo de visualización",
"Light": "Claro",
"Dark": "Oscuro",
"HighContrast": "Alto contraste",
"TextSize": "Tamaño del texto",
"Decrease": "Disminuir",
"Increase": "Aumentar",
"IndentationAmount": "Cantidad de indentación",
"Autosave": "Grabar automáticamente",
"On": "Activar",
"Off": "Desactivar",
"SketchSettings": "Configuración del bosquejo",
"SecurityProtocol": "Protocolo de seguridad",
"ServeOverHTTPS": "Usar HTTPS",
"Accessibility": "Accesibilidad",
"LintWarningSound": "Sonido de alarma Lint",
"PreviewSound": "Probar sonido",
"AccessibleTextBasedCanvas": "Lienzo accesible por texto",
"UsedScreenReader": "Uso con screen reader",
"PlainText": "Texto sin formato",
"TableText": "Tablero de texto",
"Sound": "Sonido",
"WordWrap": "Ajuste automático de línea",
"LineNumbers": "Número de línea",
"LangChange": "Lenguaje cambiado",
"Welcome": "Bienvenida",
"Login": "Ingresa",
"LoginOr": "o",
"SignUp": "registráte",
"email": "correo electrónico",
"username": "nombre de usuario",
"LoginGithub": "Ingresa con Github",
"LoginGoogle": "Ingresa con Google",
"DontHaveAccount": "No tienes cuenta?",
"ForgotPassword": "¿Olvidaste tu contraseña?",
"ResetPassword": "Regenera tu contraseña",
"BackEditor": "Regresa al editor",
"UsernameSplit": "Nombre de usuario",
"Password": "Contraseña",
"ConfirmPassword": "Confirma la contraseña",
"OpenedNewSketch": "Creaste nuevo bosquejo.",
"Hello": "Hola",
"MyAccount": "Mi Cuenta",
"My": "Mi",
"MySketches": "Mis bosquejos",
"MyCollections":"Mis colecciones",
"Asset": "Asset",
"MyAssets": "Mis assets",
"TitleAbout": "Editor Web p5.js | Acerca de",
"CodeEditing": "Editando Código",
"Error": "Error",
"In order to save": "Para guardar",
"you must be logged in": "debes ingresar a tu cuenta",
"Please": "Por favor",
"Find in files": "Encontrar en archivos",
"Create": "Create",
"enter a name": "enter a name",
"Add": "Add",
"Folder": "Directorio",
"FindText": "Encontrar texto",
"FindNextTextMatch": "Encontrar la siguiente ocurrencia de texto",
"FindPreviousTextMatch": "Encontrar la ocurrencia previa de texto",
"Code editing keyboard shortcuts follow": "Los atajos para edición son como",
"Sublime Text shortcuts": "los atajos de Sublime Text ",
"WarningUnsavedChanges": "¿Estás seguro de que quieres salir de la página? Tienes cambios sin guardar."
}