Merge remote-tracking branch 'origin/develop' into release/2023-02-27

This commit is contained in:
corihudson 2023-02-27 19:11:43 +00:00
commit ee015c12d3
93 changed files with 1877 additions and 659 deletions

1346
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -59,9 +59,11 @@
"scratch-storage": "2.0.2"
},
"devDependencies": {
"@formatjs/intl-locale": "2.4.34",
"@formatjs/intl-pluralrules": "4.1.0",
"@formatjs/intl-relativetimeformat": "8.1.8",
"@formatjs/intl-datetimeformat": "6.4.3",
"@formatjs/intl-locale": "3.0.11",
"@formatjs/intl-numberformat": "8.3.3",
"@formatjs/intl-pluralrules": "5.1.8",
"@formatjs/intl-relativetimeformat": "11.1.8",
"async": "3.2.2",
"autoprefixer": "10.4.2",
"babel-cli": "6.26.0",
@ -118,7 +120,7 @@
"query-string": "5.1.1",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-intl": "2.9.0",
"react-intl": "5.25.1",
"react-modal": "3.11.1",
"react-onclickoutside": "6.7.1",
"react-plotly.js": "2.4.0",
@ -134,8 +136,8 @@
"regenerator-runtime": "0.13.9",
"sass": "1.49.7",
"sass-loader": "10.2.1",
"scratch-gui": "1.3.10",
"scratch-l10n": "3.15.20230212032126",
"scratch-gui": "1.3.21",
"scratch-l10n": "3.15.20230227032200",
"selenium-webdriver": "4.1.0",
"slick-carousel": "1.6.0",
"style-loader": "0.12.3",

View file

@ -1,5 +1,4 @@
const classNames = require('classnames');
const FormattedRelative = require('react-intl').FormattedRelative;
const PropTypes = require('prop-types');
const React = require('react');
@ -13,21 +12,12 @@ const CommentText = props => (
className="mod-comment"
text={props.comment}
/>
{typeof props.datetimeCreated === 'undefined' ? [] : [
<p
className="comment-text-timestamp"
key="comment-text-timestamp"
>
<FormattedRelative value={new Date(props.datetimeCreated)} />
</p>
]}
</div>
);
CommentText.propTypes = {
className: PropTypes.string,
comment: PropTypes.string.isRequired,
datetimeCreated: PropTypes.string
comment: PropTypes.string.isRequired
};
module.exports = CommentText;

View file

@ -35,9 +35,3 @@
margin: 0;
overflow: hidden;
}
.comment-text-timestamp {
margin: 1rem 0 0;
color: $ui-dark-gray;
font-size: .8rem;
}

View file

@ -1,15 +1,14 @@
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const FlexRow = require('../../../flex-row/flex-row.jsx');
const FooterBox = require('../../container/footer.jsx');
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
const {getLocale} = require('../../../../lib/locales.js');
require('../footer.scss');
const ConferenceFooter = props => (
const ConferenceFooter = () => (
<FooterBox>
<FlexRow className="scratch-links">
<div className="family">
@ -86,12 +85,8 @@ const ConferenceFooter = props => (
</div>
</div>
</FlexRow>
<LanguageChooser locale={props.intl.locale} />
<LanguageChooser locale={getLocale()} />
</FooterBox>
);
ConferenceFooter.propTypes = {
intl: intlShape
};
module.exports = injectIntl(ConferenceFooter);
module.exports = ConferenceFooter;

View file

@ -1,15 +1,14 @@
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
const FlexRow = require('../../../flex-row/flex-row.jsx');
const FooterBox = require('../../container/footer.jsx');
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
const {getLocale} = require('../../../../lib/locales.js');
require('../footer.scss');
const ConferenceFooter = props => (
const ConferenceFooter = () => (
<FooterBox>
<div className="collaborators">
<h4>Sponsors</h4>
@ -213,12 +212,8 @@ const ConferenceFooter = props => (
</div>
</div>
</FlexRow>
<LanguageChooser locale={props.intl.locale} />
<LanguageChooser locale={getLocale()} />
</FooterBox>
);
ConferenceFooter.propTypes = {
intl: intlShape
};
module.exports = injectIntl(ConferenceFooter);
module.exports = ConferenceFooter;

View file

@ -1,15 +1,14 @@
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const FlexRow = require('../../../flex-row/flex-row.jsx');
const FooterBox = require('../../container/footer.jsx');
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
const {getLocale} = require('../../../../lib/locales');
require('../footer.scss');
const ConferenceFooter = props => (
const ConferenceFooter = () => (
<FooterBox>
<FlexRow className="scratch-links">
<div className="family">
@ -107,12 +106,8 @@ const ConferenceFooter = props => (
</div>
</div>
</FlexRow>
<LanguageChooser locale={props.intl.locale} />
<LanguageChooser locale={getLocale()} />
</FooterBox>
);
ConferenceFooter.propTypes = {
intl: intlShape
};
module.exports = injectIntl(ConferenceFooter);
module.exports = ConferenceFooter;

View file

@ -1,13 +1,11 @@
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
const PropTypes = require('prop-types');
const FlexRow = require('../../../flex-row/flex-row.jsx');
const FooterBox = require('../../container/footer.jsx');
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
const {getLocale} = require('../../../../lib/locales.js');
require('../footer.scss');
@ -146,7 +144,7 @@ const ConferenceFooter = props => (
</div>
</div>
</FlexRow>
<LanguageChooser locale={props.intl.locale} />
<LanguageChooser locale={getLocale()} />
<div className="organized-by-message">
<FormattedMessage id={props.organizedByMsgId} />
</div>
@ -154,8 +152,7 @@ const ConferenceFooter = props => (
);
ConferenceFooter.propTypes = {
intl: intlShape,
organizedByMsgId: PropTypes.string
};
module.exports = injectIntl(ConferenceFooter);
module.exports = ConferenceFooter;

View file

@ -1,6 +1,5 @@
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const MediaQuery = require('react-responsive').default;
const connect = require('react-redux').connect;
const PropTypes = require('prop-types');
@ -10,6 +9,8 @@ const FooterBox = require('../container/footer.jsx');
const LanguageChooser = require('../../languagechooser/languagechooser.jsx');
const frameless = require('../../../lib/frameless');
const intlShape = require('../../../lib/intl-shape');
const {getLocale} = require('../../../lib/locales.js');
const getScratchWikiLink = require('../../../lib/scratch-wiki');
require('./footer.scss');
@ -213,12 +214,12 @@ const Footer = props => (
</dl>
</div>
</MediaQuery>
<LanguageChooser locale={props.intl.locale} />
<LanguageChooser locale={getLocale()} />
</FooterBox>
);
Footer.propTypes = {
intl: intlShape.isRequired,
intl: intlShape.isRequired, // eslint-disable-line react/no-unused-prop-types
scratchWikiLink: PropTypes.string
};

View file

@ -8,10 +8,10 @@ const ReactPhoneInput = require('react-telephone-input/lib/withStyles').default;
const Row = require('formsy-react-components').Row;
const Help = require('formsy-react-components/release/components/help').default;
const ErrorMessages = require('formsy-react-components/release/components/error-messages').default;
const intl = require('react-intl');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
const intl = require('../../lib/intl.jsx');
const validationHOCFactory = require('./validations.jsx').validationHOCFactory;
require('./row.scss');

View file

@ -1,5 +1,5 @@
const defaults = require('lodash.defaultsdeep');
const intl = require('../../lib/intl.jsx');
const intl = require('react-intl');
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');

View file

@ -1,13 +1,14 @@
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const bindAll = require('lodash.bindall');
const connect = require('react-redux').connect;
const PropTypes = require('prop-types');
const React = require('react');
const intlShape = require('../../lib/intl-shape');
const Button = require('../forms/button.jsx');
const Spinner = require('../spinner/spinner.jsx');
require('./helpwidget.scss');
// map Scratch locale to supported Freshdesk locale

View file

@ -3,9 +3,10 @@ const classNames = require('classnames');
const React = require('react');
const PropTypes = require('prop-types');
import {Formik} from 'formik';
const {injectIntl, intlShape} = require('react-intl');
const {injectIntl} = require('react-intl');
const FormattedMessage = require('react-intl').FormattedMessage;
const intlShape = require('../../lib/intl-shape');
const FormikSelect = require('../../components/formik-forms/formik-select.jsx');
const JoinFlowStep = require('./join-flow-step.jsx');
const InfoButton = require('../info-button/info-button.jsx');

View file

@ -3,9 +3,10 @@ const classNames = require('classnames');
const React = require('react');
const PropTypes = require('prop-types');
import {Formik} from 'formik';
const {injectIntl, intlShape} = require('react-intl');
const {injectIntl} = require('react-intl');
const countryData = require('../../lib/country-data');
const intlShape = require('../../lib/intl-shape');
const FormikSelect = require('../../components/formik-forms/formik-select.jsx');
const JoinFlowStep = require('./join-flow-step.jsx');
const FormikCheckbox = require('../../components/formik-forms/formik-checkbox.jsx');

View file

@ -3,14 +3,16 @@ const classNames = require('classnames');
const React = require('react');
const PropTypes = require('prop-types');
import {Formik} from 'formik';
const {injectIntl, intlShape} = require('react-intl');
const {injectIntl} = require('react-intl');
const FormattedMessage = require('react-intl').FormattedMessage;
const intlShape = require('../../lib/intl-shape');
const validate = require('../../lib/validate');
const JoinFlowStep = require('./join-flow-step.jsx');
const FormikInput = require('../../components/formik-forms/formik-input.jsx');
const InfoButton = require('../info-button/info-button.jsx');
const Captcha = require('../../components/captcha/captcha.jsx');
require('./join-flow-steps.scss');
class EmailStep extends React.Component {

View file

@ -3,9 +3,10 @@ const classNames = require('classnames');
const React = require('react');
const PropTypes = require('prop-types');
import {Formik} from 'formik';
const {injectIntl, intlShape} = require('react-intl');
const {injectIntl} = require('react-intl');
const FormattedMessage = require('react-intl').FormattedMessage;
const intlShape = require('../../lib/intl-shape');
const FormikRadioButton = require('../../components/formik-forms/formik-radio-button.jsx');
const JoinFlowStep = require('./join-flow-step.jsx');
const InfoButton = require('../info-button/info-button.jsx');

View file

@ -3,10 +3,10 @@ const connect = require('react-redux').connect;
const defaults = require('lodash.defaultsdeep');
const PropTypes = require('prop-types');
const React = require('react');
const injectIntl = require('react-intl').injectIntl;
const api = require('../../lib/api');
const injectIntl = require('../../lib/intl.jsx').injectIntl;
const intlShape = require('../../lib/intl.jsx').intlShape;
const intlShape = require('../../lib/intl-shape');
const sessionActions = require('../../redux/session.js');
const validate = require('../../lib/validate');

View file

@ -3,7 +3,7 @@ const React = require('react');
const PropTypes = require('prop-types');
const injectIntl = require('react-intl').injectIntl;
const intl = require('../../lib/intl.jsx');
const intlShape = require('../../lib/intl-shape');
const Spinner = require('../../components/spinner/spinner.jsx');
const ModalTitle = require('../modal/base/modal-title.jsx');
@ -29,7 +29,7 @@ const NextStepButton = props => (
NextStepButton.propTypes = {
content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
intl: intl.intlShape,
intl: intlShape,
waiting: PropTypes.bool
};

View file

@ -2,8 +2,9 @@ const bindAll = require('lodash.bindall');
const React = require('react');
const PropTypes = require('prop-types');
const FormattedMessage = require('react-intl').FormattedMessage;
const {injectIntl, intlShape} = require('react-intl');
const {injectIntl} = require('react-intl');
const intlShape = require('../../lib/intl-shape');
const JoinFlowStep = require('./join-flow-step.jsx');
require('./join-flow-steps.scss');

View file

@ -3,8 +3,9 @@ const classNames = require('classnames');
const React = require('react');
const PropTypes = require('prop-types');
import {Formik} from 'formik';
const {injectIntl, intlShape} = require('react-intl');
const {injectIntl} = require('react-intl');
const intlShape = require('../../lib/intl-shape');
const validate = require('../../lib/validate');
const FormikInput = require('../../components/formik-forms/formik-input.jsx');
const FormikCheckbox = require('../../components/formik-forms/formik-checkbox.jsx');

View file

@ -3,8 +3,9 @@ const React = require('react');
const PropTypes = require('prop-types');
import {Formik} from 'formik';
const FormattedMessage = require('react-intl').FormattedMessage;
const {injectIntl, intlShape} = require('react-intl');
const {injectIntl} = require('react-intl');
const intlShape = require('../../lib/intl-shape');
const JoinFlowStep = require('./join-flow-step.jsx');
require('./join-flow-steps.scss');

View file

@ -1,10 +1,10 @@
const bindAll = require('lodash.bindall');
const classNames = require('classnames');
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const intlShape = require('../../lib/intl-shape');
const jar = require('../../lib/jar.js');
const languages = require('scratch-l10n').default;
const Form = require('../forms/form.jsx');

View file

@ -3,8 +3,8 @@ const connect = require('react-redux').connect;
const FormattedMessage = require('react-intl').FormattedMessage;
const PropTypes = require('prop-types');
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const intlShape = require('../../lib/intl-shape');
const navigationActions = require('../../redux/navigation.js');
const Modal = require('../modal/base/modal.jsx');

View file

@ -2,9 +2,9 @@ const PropTypes = require('prop-types');
const React = require('react');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const Modal = require('../base/modal.jsx');
const intlShape = require('../../../lib/intl-shape');
const Form = require('../../forms/form.jsx');
const Button = require('../../forms/button.jsx');
const Spinner = require('../../spinner/spinner.jsx');

View file

@ -2,9 +2,9 @@ const PropTypes = require('prop-types');
const React = require('react');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const Modal = require('../base/modal.jsx');
const intlShape = require('../../../lib/intl-shape');
const Button = require('../../forms/button.jsx');
const FlexRow = require('../../flex-row/flex-row.jsx');

View file

@ -2,9 +2,9 @@ const PropTypes = require('prop-types');
const React = require('react');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const Modal = require('../base/modal.jsx');
const intlShape = require('../../../lib/intl-shape');
const Button = require('../../forms/button.jsx');
const FlexRow = require('../../flex-row/flex-row.jsx');

View file

@ -3,7 +3,6 @@ const PropTypes = require('prop-types');
const React = require('react');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const Modal = require('../base/modal.jsx');
const ModalInnerContent = require('../base/modal-inner-content.jsx');
const Button = require('../../forms/button.jsx');
@ -12,9 +11,11 @@ const FlexRow = require('../../flex-row/flex-row.jsx');
const MuteStep = require('./mute-step.jsx');
const FeedbackForm = require('./feedback-form.jsx');
const classNames = require('classnames');
require('./modal.scss');
const api = require('../../../lib/api');
const intlShape = require('../../../lib/intl-shape');
require('./modal.scss');
const steps = {
COMMENT_ISSUE: 0,

View file

@ -4,9 +4,9 @@ const React = require('react');
const connect = require('react-redux').connect;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const Modal = require('../base/modal.jsx');
const intlShape = require('../../../lib/intl-shape');
const ModalTitle = require('../base/modal-title.jsx');
const ModalInnerContent = require('../base/modal-inner-content.jsx');
const Select = require('../../forms/select.jsx');

View file

@ -1,9 +1,9 @@
const PropTypes = require('prop-types');
const React = require('react');
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const classNames = require('classnames');
const intlShape = require('../../../lib/intl-shape');
const Modal = require('../base/modal.jsx');
const ModalTitle = require('../base/modal-title.jsx');
const ModalInnerContent = require('../base/modal-inner-content.jsx');

View file

@ -3,10 +3,10 @@ const classNames = require('classnames');
const connect = require('react-redux').connect;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const intlShape = require('../../../lib/intl-shape');
const messageCountActions = require('../../../redux/message-count.js');
const navigationActions = require('../../../redux/navigation.js');
const sessionActions = require('../../../redux/session.js');

View file

@ -1,11 +1,11 @@
/* eslint-disable react/no-multi-comp */
const bindAll = require('lodash.bindall');
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const intl = require('react-intl');
const intl = require('../../lib/intl.jsx');
const intlShape = require('../../lib/intl-shape');
const Card = require('../../components/card/card.jsx');
const Checkbox = require('../../components/forms/checkbox.jsx');

View file

@ -1,14 +1,13 @@
/* eslint-disable react/no-multi-comp */
const bindAll = require('lodash.bindall');
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const {injectIntl, FormattedMessage} = require('react-intl');
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
const api = require('../../lib/api');
const countryData = require('../../lib/country-data');
const intl = require('../../lib/intl.jsx');
const intlShape = require('../../lib/intl-shape');
const Avatar = require('../../components/avatar/avatar.jsx');
const Button = require('../../components/forms/button.jsx');
@ -174,7 +173,7 @@ class UsernameStep extends React.Component {
{this.props.title ? (
this.props.title
) : (
<intl.FormattedMessage id="registration.usernameStepTitle" />
<FormattedMessage id="registration.usernameStepTitle" />
)}
</h2>
<p className="description">
@ -182,9 +181,9 @@ class UsernameStep extends React.Component {
this.props.description
) : (
<span>
<intl.FormattedMessage id="registration.usernameStepDescription" />&nbsp;
<FormattedMessage id="registration.usernameStepDescription" />&nbsp;
<b>
<intl.FormattedMessage id="registration.usernameStepRealName" />
<FormattedMessage id="registration.usernameStepRealName" />
</b>
</span>
)}
@ -281,7 +280,7 @@ class UsernameStep extends React.Component {
/>
<GeneralError name="all" />
<NextStepButton
text={<intl.FormattedMessage id="registration.nextStep" />}
text={<FormattedMessage id="registration.nextStep" />}
waiting={this.props.waiting || this.state.waiting}
/>
</Form>
@ -298,7 +297,7 @@ class UsernameStep extends React.Component {
UsernameStep.propTypes = {
activeStep: PropTypes.number,
description: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
intl: intl.intlShape,
intl: intlShape,
onNextStep: PropTypes.func,
showPassword: PropTypes.bool,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
@ -339,7 +338,7 @@ class ChoosePasswordStep extends React.Component {
{this.props.intl.formatMessage({id: 'registration.choosePasswordStepTitle'})}
</h2>
<p className="description">
<intl.FormattedMessage id="registration.choosePasswordStepDescription" />
<FormattedMessage id="registration.choosePasswordStepDescription" />
<Tooltip
tipContent={
this.props.intl.formatMessage({id: 'registration.choosePasswordStepTooltip'})
@ -384,7 +383,7 @@ class ChoosePasswordStep extends React.Component {
onChange={this.handleChangeShowPassword}
/>
<NextStepButton
text={<intl.FormattedMessage id="registration.nextStep" />}
text={<FormattedMessage id="registration.nextStep" />}
waiting={this.props.waiting || this.state.waiting}
/>
</Form>
@ -501,12 +500,12 @@ class DemographicsStep extends React.Component {
return (
<Slide className="registration-step demographics-step">
<h2>
<intl.FormattedMessage id="registration.personalStepTitle" />
<FormattedMessage id="registration.personalStepTitle" />
</h2>
<p className="description">
{this.props.description ?
this.props.description :
<intl.FormattedMessage id="registration.personalStepDescription" />
<FormattedMessage id="registration.personalStepDescription" />
}
<Tooltip
tipContent={
@ -589,7 +588,7 @@ class DemographicsStep extends React.Component {
valueLabel="I'm a robot!"
/>
<NextStepButton
text={<intl.FormattedMessage id="registration.nextStep" />}
text={<FormattedMessage id="registration.nextStep" />}
waiting={this.props.waiting}
/>
</Form>
@ -629,10 +628,10 @@ const IntlDemographicsStep = injectIntl(DemographicsStep);
const NameStep = props => (
<Slide className="registration-step name-step">
<h2>
<intl.FormattedHTMLMessage id="teacherRegistration.nameStepTitle" />
<FormattedMessage id="teacherRegistration.nameStepTitleNew" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.nameStepDescription" />
<FormattedMessage id="teacherRegistration.nameStepDescription" />
<Tooltip
tipContent={
props.intl.formatMessage({id: 'registration.nameStepTooltip'})
@ -675,7 +674,7 @@ const NameStep = props => (
}}
/>
<NextStepButton
text={<intl.FormattedMessage id="registration.nextStep" />}
text={<FormattedMessage id="registration.nextStep" />}
waiting={props.waiting}
/>
</Form>
@ -748,10 +747,10 @@ class OrganizationStep extends React.Component {
return (
<Slide className="registration-step organization-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.organization" />
<FormattedMessage id="teacherRegistration.organization" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.privacyDescription" />
<FormattedMessage id="teacherRegistration.privacyDescription" />
<Tooltip
tipContent={
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
@ -792,9 +791,9 @@ class OrganizationStep extends React.Component {
}}
/>
<div className="organization-type">
<b className="row-label"><intl.FormattedMessage id="teacherRegistration.orgType" /></b>
<b className="row-label"><FormattedMessage id="teacherRegistration.orgType" /></b>
<p className="help-text">
<intl.FormattedMessage id="teacherRegistration.checkAll" />
<FormattedMessage id="teacherRegistration.checkAll" />
</p>
<CheckboxGroup
required
@ -833,9 +832,9 @@ class OrganizationStep extends React.Component {
/>
</div>
<div className="url-input">
<b className="row-label"><intl.FormattedMessage id="general.website" /></b>
<b className="row-label"><FormattedMessage id="general.website" /></b>
<p className="help-text">
<intl.FormattedMessage id="teacherRegistration.notRequired" />
<FormattedMessage id="teacherRegistration.notRequired" />
</p>
<Input
name="organization.url"
@ -853,7 +852,7 @@ class OrganizationStep extends React.Component {
/>
</div>
<NextStepButton
text={<intl.FormattedMessage id="registration.nextStep" />}
text={<FormattedMessage id="registration.nextStep" />}
waiting={this.props.waiting}
/>
</Form>
@ -906,10 +905,10 @@ class AddressStep extends React.Component {
return (
<Slide className="registration-step address-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.addressStepTitle" />
<FormattedMessage id="teacherRegistration.addressStepTitle" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.privacyDescription" />
<FormattedMessage id="teacherRegistration.privacyDescription" />
<Tooltip
tipContent={
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
@ -988,10 +987,10 @@ class AddressStep extends React.Component {
/> : []
}
<b className="row-label">
<intl.FormattedMessage id="teacherRegistration.zipCode" />
<FormattedMessage id="teacherRegistration.zipCode" />
</b>
{this.state.countryChoice === 'us' ? [] : <p className="help-text">
<intl.FormattedMessage id="teacherRegistration.notRequired" />
<FormattedMessage id="teacherRegistration.notRequired" />
</p>}
<Input
name="address.zip"
@ -1008,7 +1007,7 @@ class AddressStep extends React.Component {
/>
<GeneralError name="all" />
<NextStepButton
text={<intl.FormattedMessage id="registration.nextStep" />}
text={<FormattedMessage id="registration.nextStep" />}
waiting={this.props.waiting || this.state.waiting}
/>
</Form>
@ -1062,10 +1061,10 @@ class UseScratchStep extends React.Component {
return (
<Slide className="registration-step usescratch-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.useScratchStepTitle" />
<FormattedMessage id="teacherRegistration.useScratchStepTitle" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.useScratchStepDescription" />
<FormattedMessage id="teacherRegistration.useScratchStepDescription" />
<Tooltip
tipContent={
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
@ -1098,7 +1097,7 @@ class UseScratchStep extends React.Component {
maxCharacters={this.props.maxCharacters}
/>
<NextStepButton
text={<intl.FormattedMessage id="registration.nextStep" />}
text={<FormattedMessage id="registration.nextStep" />}
waiting={this.props.waiting}
/>
</Form>
@ -1192,10 +1191,10 @@ class EmailStep extends React.Component {
return (
<Slide className="registration-step email-step">
<h2>
<intl.FormattedMessage id="teacherRegistration.emailStepTitle" />
<FormattedMessage id="teacherRegistration.emailStepTitle" />
</h2>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.emailStepDescription" />
<FormattedMessage id="teacherRegistration.emailStepDescription" />
<Tooltip
tipContent={
this.props.intl.formatMessage({id: 'registration.nameStepTooltip'})
@ -1237,7 +1236,7 @@ class EmailStep extends React.Component {
/>
<GeneralError name="all" />
<NextStepButton
text={<intl.FormattedMessage id="registration.nextStep" />}
text={<FormattedMessage id="registration.nextStep" />}
waiting={this.props.waiting}
/>
</Form>
@ -1272,6 +1271,7 @@ EmailStep.defaultProps = {
const IntlEmailStep = injectIntl(EmailStep);
const EducatorResourcesLink = chunks => <a href="/educators#resources">{chunks}</a>;
/*
* TEACHER APPROVAL STEP
@ -1279,17 +1279,17 @@ const IntlEmailStep = injectIntl(EmailStep);
const TeacherApprovalStep = props => (
<Slide className="registration-step last-step">
<h2>
<intl.FormattedMessage id="registration.lastStepTitle" />
<FormattedMessage id="registration.lastStepTitle" />
</h2>
<p className="description">
<intl.FormattedMessage id="registration.lastStepDescription" />
<FormattedMessage id="registration.lastStepDescription" />
</p>
{props.confirmed || !props.email ?
[] : (
<Card className="confirm">
<h4><intl.FormattedMessage id="registration.confirmYourEmail" /></h4>
<h4><FormattedMessage id="registration.confirmYourEmail" /></h4>
<p>
<intl.FormattedMessage id="registration.confirmYourEmailDescription" /><br />
<FormattedMessage id="registration.confirmYourEmailDescription" /><br />
<strong>{props.email}</strong>
</p>
</Card>
@ -1297,16 +1297,19 @@ const TeacherApprovalStep = props => (
}
{props.invited ?
<Card className="wait">
<h4><intl.FormattedMessage id="registration.waitForApproval" /></h4>
<h4><FormattedMessage id="registration.waitForApproval" /></h4>
<p>
<intl.FormattedMessage id="registration.waitForApprovalDescription" />
<FormattedMessage id="registration.waitForApprovalDescription" />
</p>
</Card> : []
}
<Card className="resources">
<h4><intl.FormattedMessage id="registration.checkOutResources" /></h4>
<h4><FormattedMessage id="registration.checkOutResources" /></h4>
<p>
<intl.FormattedHTMLMessage id="registration.checkOutResourcesDescription" />
<FormattedMessage
id="registration.checkOutResourcesDescriptionHTML"
values={{a: EducatorResourcesLink}}
/>
</p>
</Card>
</Slide>

View file

@ -0,0 +1,39 @@
const React = require('react');
const {useEffect, useState} = React;
const PropTypes = require('prop-types');
const {FormattedRelativeTime} = require('react-intl');
const {selectUnit} = require('../../lib/select-unit');
const RelativeTime = ({value}) => {
const [selectedUnit, setSelectedUnit] = useState(selectUnit(value));
useEffect(() => {
// It is unlikely that users will leave this running for days. Don't
// auto-update beyond hours.
if (!['second', 'minute', 'hour'].includes(selectedUnit.unit)) return;
const timerId = setInterval(() => {
const nextSelectedUnit = selectUnit(value);
if (selectedUnit.value !== nextSelectedUnit.value ||
selectUnit.unit !== nextSelectedUnit.unit) {
setSelectedUnit(nextSelectedUnit);
}
}, 10000);
return () => clearTimeout(timerId);
}, [value, selectedUnit]);
return (
<FormattedRelativeTime
value={selectedUnit.value}
unit={selectedUnit.unit}
numeric="auto"
/>
);
};
RelativeTime.propTypes = {
value: PropTypes.instanceOf(Date)
};
module.exports = RelativeTime;

View file

@ -1,9 +1,9 @@
const classNames = require('classnames');
const FormattedRelative = require('react-intl').FormattedRelative;
const PropTypes = require('prop-types');
const React = require('react');
const FlexRow = require('../flex-row/flex-row.jsx');
const RelativeTime = require('../relative-time/relative-time.jsx');
require('./social-message.scss');
@ -24,7 +24,7 @@ const SocialMessage = props => (
</div>
</div>
<span className="social-message-date">
<FormattedRelative value={new Date(props.datetime)} />
<RelativeTime value={new Date(props.datetime)} />
</span>
</FlexRow>
</props.as>

View file

@ -1,13 +1,12 @@
import 'regenerator-runtime/runtime'; // Needed for async/await
const jar = require('./lib/jar');
import intlPolyfill from './lib/intl-polyfill';
/**
* -----------------------------------------------------------------------------
* L10N
* -----------------------------------------------------------------------------
*/
(async () => {
(() => {
/*
* Bind locale code from cookie if available. Uses navigator language API as a fallback.
*
@ -37,7 +36,6 @@ import intlPolyfill from './lib/intl-polyfill';
window._locale = updateLocale();
document.documentElement.lang = window._locale;
await intlPolyfill(window._locale);
})();
/**

View file

@ -166,6 +166,7 @@
"registration.cantCreateAccount": "Scratch could not create your account.",
"registration.checkOutResources": "Get Started with Resources",
"registration.checkOutResourcesDescription": "Explore materials for educators and facilitators written by the Scratch Team, including <a href='/educators#resources'>tips, tutorials, and guides</a>.",
"registration.checkOutResourcesDescriptionHTML": "Explore materials for educators and facilitators written by the Scratch Team, including <a>tips, tutorials, and guides</a>.",
"registration.choosePasswordStepDescription": "Type in a new password for your account. You will use this password the next time you log into Scratch.",
"registration.choosePasswordStepTitle": "Create a password",
"registration.choosePasswordStepTooltip": "Don't use your name or anything that's easy for someone else to guess.",

View file

@ -1,9 +1,9 @@
// this file should only be `required` in the format-time
// when Intl.RelativeTimeFormat is not available (Safari < 14), but
// we're not currently able to do the code splitting in www, and it
// is always included. To reduce the amount of data that's loaded limit
// the number of languages loaded to just the top few that are still using
// safari <14. These seven account for most uses.
// This file polyfills the required Intl objects and locale data.
// The react-intl library uses PluralRules, RelativeTimeFormat, NumberFormat,
// and DateTimeFormat. Even if browsers support these objects, it is possible
// that the browser does not support the specific locale.
// There are a small number of Scratch locales that do not have polyfill locale
// data available. See /src/lib/locales.js for how they are handled.
// relativetimeformat depends on locale which also needs to be polyfilled in
// safari <14
// The plural rules is required for safari 12.
@ -11,6 +11,8 @@ import 'regenerator-runtime/runtime'; // Needed for async/await
import {shouldPolyfill as shouldPolyfillLocale} from '@formatjs/intl-locale/should-polyfill';
import {shouldPolyfill as shouldPolyfillRelativeTimeFormat} from '@formatjs/intl-relativetimeformat/should-polyfill';
import {shouldPolyfill as shouldPolyfillPluralRules} from '@formatjs/intl-pluralrules/should-polyfill';
import {shouldPolyfill as shouldPolyfillNumberFormat} from '@formatjs/intl-numberformat/should-polyfill';
import {shouldPolyfill as shouldPolyfillDateTimeFormat} from '@formatjs/intl-datetimeformat/should-polyfill';
/**
* polyfill all the parts needed from intl
* @param {string} locale currently selected locale
@ -19,293 +21,458 @@ import {shouldPolyfill as shouldPolyfillPluralRules} from '@formatjs/intl-plural
const intlPolyfill = async function (locale) {
if (!(shouldPolyfillLocale() ||
shouldPolyfillPluralRules(locale) ||
shouldPolyfillRelativeTimeFormat(locale))) {
shouldPolyfillRelativeTimeFormat(locale) ||
shouldPolyfillNumberFormat(locale) ||
shouldPolyfillDateTimeFormat(locale))) {
return;
}
if (shouldPolyfillRelativeTimeFormat(locale)) {
await import('@formatjs/intl-relativetimeformat/polyfill');
if (shouldPolyfillLocale()) {
await import('@formatjs/intl-locale/polyfill-force');
}
if (shouldPolyfillPluralRules(locale)) {
await import('@formatjs/intl-pluralrules/polyfill');
await import('@formatjs/intl-pluralrules/polyfill-force');
}
if (shouldPolyfillLocale(locale)) {
await import('@formatjs/intl-locale/polyfill');
if (shouldPolyfillRelativeTimeFormat(locale)) {
await import('@formatjs/intl-relativetimeformat/polyfill-force');
}
if (shouldPolyfillNumberFormat(locale)) {
await import('@formatjs/intl-numberformat/polyfill-force');
}
if (shouldPolyfillDateTimeFormat(locale)) {
await import('@formatjs/intl-datetimeformat/polyfill-force');
}
switch (locale.toLowerCase().split('-')[0]) {
case 'af':
await import('@formatjs/intl-relativetimeformat/locale-data/af');
await import('@formatjs/intl-pluralrules/locale-data/af');
await import('@formatjs/intl-relativetimeformat/locale-data/af');
await import('@formatjs/intl-numberformat/locale-data/af');
await import('@formatjs/intl-datetimeformat/locale-data/af');
break;
case 'ar':
await import('@formatjs/intl-relativetimeformat/locale-data/ar');
await import('@formatjs/intl-pluralrules/locale-data/ar');
await import('@formatjs/intl-relativetimeformat/locale-data/ar');
await import('@formatjs/intl-numberformat/locale-data/ar');
await import('@formatjs/intl-datetimeformat/locale-data/ar');
break;
case 'am':
await import('@formatjs/intl-relativetimeformat/locale-data/am');
await import('@formatjs/intl-pluralrules/locale-data/am');
await import('@formatjs/intl-relativetimeformat/locale-data/am');
await import('@formatjs/intl-numberformat/locale-data/am');
await import('@formatjs/intl-datetimeformat/locale-data/am');
break;
case 'an':
await import('@formatjs/intl-relativetimeformat/locale-data/en');
await import('@formatjs/intl-pluralrules/locale-data/an');
case 'ast':
await import('@formatjs/intl-pluralrules/locale-data/ast');
await import('@formatjs/intl-relativetimeformat/locale-data/ast');
await import('@formatjs/intl-numberformat/locale-data/ast');
await import('@formatjs/intl-datetimeformat/locale-data/ast');
break;
case 'az':
await import('@formatjs/intl-relativetimeformat/locale-data/az');
await import('@formatjs/intl-pluralrules/locale-data/az');
await import('@formatjs/intl-relativetimeformat/locale-data/az');
await import('@formatjs/intl-numberformat/locale-data/az');
await import('@formatjs/intl-datetimeformat/locale-data/az');
break;
case 'id':
await import('@formatjs/intl-relativetimeformat/locale-data/id');
await import('@formatjs/intl-pluralrules/locale-data/id');
await import('@formatjs/intl-relativetimeformat/locale-data/id');
await import('@formatjs/intl-numberformat/locale-data/id');
await import('@formatjs/intl-datetimeformat/locale-data/id');
break;
case 'bn':
await import('@formatjs/intl-relativetimeformat/locale-data/bn');
await import('@formatjs/intl-pluralrules/locale-data/bn');
await import('@formatjs/intl-relativetimeformat/locale-data/bn');
await import('@formatjs/intl-numberformat/locale-data/bn');
await import('@formatjs/intl-datetimeformat/locale-data/bn');
break;
case 'be':
await import('@formatjs/intl-relativetimeformat/locale-data/be');
await import('@formatjs/intl-pluralrules/locale-data/be');
await import('@formatjs/intl-relativetimeformat/locale-data/be');
await import('@formatjs/intl-numberformat/locale-data/be');
await import('@formatjs/intl-datetimeformat/locale-data/be');
break;
case 'bg':
await import('@formatjs/intl-relativetimeformat/locale-data/bg');
await import('@formatjs/intl-pluralrules/locale-data/bg');
await import('@formatjs/intl-relativetimeformat/locale-data/bg');
await import('@formatjs/intl-numberformat/locale-data/bg');
await import('@formatjs/intl-datetimeformat/locale-data/bg');
break;
case 'ca':
await import('@formatjs/intl-relativetimeformat/locale-data/ca');
await import('@formatjs/intl-pluralrules/locale-data/ca');
await import('@formatjs/intl-relativetimeformat/locale-data/ca');
await import('@formatjs/intl-numberformat/locale-data/ca');
await import('@formatjs/intl-datetimeformat/locale-data/ca');
break;
case 'cs':
await import('@formatjs/intl-relativetimeformat/locale-data/cs');
await import('@formatjs/intl-pluralrules/locale-data/cs');
await import('@formatjs/intl-relativetimeformat/locale-data/cs');
await import('@formatjs/intl-numberformat/locale-data/cs');
await import('@formatjs/intl-datetimeformat/locale-data/cs');
break;
case 'cy':
await import('@formatjs/intl-relativetimeformat/locale-data/cy');
await import('@formatjs/intl-pluralrules/locale-data/cy');
await import('@formatjs/intl-relativetimeformat/locale-data/cy');
await import('@formatjs/intl-numberformat/locale-data/cy');
await import('@formatjs/intl-datetimeformat/locale-data/cy');
break;
case 'da':
await import('@formatjs/intl-relativetimeformat/locale-data/da');
await import('@formatjs/intl-pluralrules/locale-data/da');
await import('@formatjs/intl-relativetimeformat/locale-data/da');
await import('@formatjs/intl-numberformat/locale-data/da');
await import('@formatjs/intl-datetimeformat/locale-data/da');
break;
case 'de':
await import('@formatjs/intl-relativetimeformat/locale-data/de');
await import('@formatjs/intl-pluralrules/locale-data/de');
await import('@formatjs/intl-relativetimeformat/locale-data/de');
await import('@formatjs/intl-numberformat/locale-data/de');
await import('@formatjs/intl-datetimeformat/locale-data/de');
break;
case 'et':
await import('@formatjs/intl-relativetimeformat/locale-data/et');
await import('@formatjs/intl-pluralrules/locale-data/et');
await import('@formatjs/intl-relativetimeformat/locale-data/et');
await import('@formatjs/intl-numberformat/locale-data/et');
await import('@formatjs/intl-datetimeformat/locale-data/et');
break;
case 'el':
await import('@formatjs/intl-relativetimeformat/locale-data/el');
await import('@formatjs/intl-pluralrules/locale-data/el');
await import('@formatjs/intl-relativetimeformat/locale-data/el');
await import('@formatjs/intl-numberformat/locale-data/el');
await import('@formatjs/intl-datetimeformat/locale-data/el');
break;
case 'en':
await import('@formatjs/intl-relativetimeformat/locale-data/en');
await import('@formatjs/intl-pluralrules/locale-data/en');
await import('@formatjs/intl-relativetimeformat/locale-data/en');
await import('@formatjs/intl-numberformat/locale-data/en');
await import('@formatjs/intl-datetimeformat/locale-data/en');
break;
case 'es':
case 'rap':
case 'qu':
await import('@formatjs/intl-relativetimeformat/locale-data/es');
await import('@formatjs/intl-pluralrules/locale-data/es');
await import('@formatjs/intl-relativetimeformat/locale-data/es');
await import('@formatjs/intl-numberformat/locale-data/es');
await import('@formatjs/intl-datetimeformat/locale-data/es');
break;
case 'eo':
await import('@formatjs/intl-pluralrules/locale-data/eo');
await import('@formatjs/intl-relativetimeformat/locale-data/eo');
await import('@formatjs/intl-numberformat/locale-data/eo');
await import('@formatjs/intl-datetimeformat/locale-data/eo');
break;
case 'eu':
await import('@formatjs/intl-relativetimeformat/locale-data/eu');
await import('@formatjs/intl-pluralrules/locale-data/eu');
await import('@formatjs/intl-relativetimeformat/locale-data/eu');
await import('@formatjs/intl-numberformat/locale-data/eu');
await import('@formatjs/intl-datetimeformat/locale-data/eu');
break;
case 'fa':
await import('@formatjs/intl-relativetimeformat/locale-data/fa');
await import('@formatjs/intl-pluralrules/locale-data/fa');
await import('@formatjs/intl-relativetimeformat/locale-data/fa');
await import('@formatjs/intl-numberformat/locale-data/fa');
await import('@formatjs/intl-datetimeformat/locale-data/fa');
break;
case 'fil':
await import('@formatjs/intl-pluralrules/locale-data/fil');
await import('@formatjs/intl-relativetimeformat/locale-data/fil');
await import('@formatjs/intl-numberformat/locale-data/fil');
await import('@formatjs/intl-datetimeformat/locale-data/fil');
break;
case 'fr':
case 'ht':
await import('@formatjs/intl-relativetimeformat/locale-data/fr');
await import('@formatjs/intl-pluralrules/locale-data/fr');
await import('@formatjs/intl-relativetimeformat/locale-data/fr');
await import('@formatjs/intl-numberformat/locale-data/fr');
await import('@formatjs/intl-datetimeformat/locale-data/fr');
break;
case 'fy':
await import('@formatjs/intl-relativetimeformat/locale-data/fy');
await import('@formatjs/intl-pluralrules/locale-data/fy');
await import('@formatjs/intl-relativetimeformat/locale-data/fy');
await import('@formatjs/intl-numberformat/locale-data/fy');
await import('@formatjs/intl-datetimeformat/locale-data/fy');
break;
case 'ga':
await import('@formatjs/intl-relativetimeformat/locale-data/ga');
await import('@formatjs/intl-pluralrules/locale-data/ga');
await import('@formatjs/intl-relativetimeformat/locale-data/ga');
await import('@formatjs/intl-numberformat/locale-data/ga');
await import('@formatjs/intl-datetimeformat/locale-data/ga');
break;
case 'gd':
await import('@formatjs/intl-relativetimeformat/locale-data/gd');
await import('@formatjs/intl-pluralrules/locale-data/gd');
await import('@formatjs/intl-relativetimeformat/locale-data/gd');
await import('@formatjs/intl-numberformat/locale-data/gd');
await import('@formatjs/intl-datetimeformat/locale-data/gd');
break;
case 'gl':
await import('@formatjs/intl-relativetimeformat/locale-data/gl');
await import('@formatjs/intl-pluralrules/locale-data/gl');
await import('@formatjs/intl-relativetimeformat/locale-data/gl');
await import('@formatjs/intl-numberformat/locale-data/gl');
await import('@formatjs/intl-datetimeformat/locale-data/gl');
break;
case 'ko':
await import('@formatjs/intl-relativetimeformat/locale-data/ko');
await import('@formatjs/intl-pluralrules/locale-data/ko');
await import('@formatjs/intl-relativetimeformat/locale-data/ko');
await import('@formatjs/intl-numberformat/locale-data/ko');
await import('@formatjs/intl-datetimeformat/locale-data/ko');
break;
case 'ha':
await import('@formatjs/intl-pluralrules/locale-data/ha');
await import('@formatjs/intl-relativetimeformat/locale-data/ha');
await import('@formatjs/intl-numberformat/locale-data/ha');
await import('@formatjs/intl-datetimeformat/locale-data/ha');
break;
case 'hy':
await import('@formatjs/intl-relativetimeformat/locale-data/hy');
await import('@formatjs/intl-pluralrules/locale-data/hy');
await import('@formatjs/intl-relativetimeformat/locale-data/hy');
await import('@formatjs/intl-numberformat/locale-data/hy');
await import('@formatjs/intl-datetimeformat/locale-data/hy');
break;
case 'he':
await import('@formatjs/intl-relativetimeformat/locale-data/he');
await import('@formatjs/intl-pluralrules/locale-data/he');
await import('@formatjs/intl-relativetimeformat/locale-data/he');
await import('@formatjs/intl-numberformat/locale-data/he');
await import('@formatjs/intl-datetimeformat/locale-data/he');
break;
case 'hr':
await import('@formatjs/intl-relativetimeformat/locale-data/hr');
await import('@formatjs/intl-pluralrules/locale-data/hr');
await import('@formatjs/intl-relativetimeformat/locale-data/hr');
await import('@formatjs/intl-numberformat/locale-data/hr');
await import('@formatjs/intl-datetimeformat/locale-data/hr');
break;
case 'xh':
await import('@formatjs/intl-relativetimeformat/locale-data/xh');
await import('@formatjs/intl-pluralrules/locale-data/xh');
await import('@formatjs/intl-relativetimeformat/locale-data/xh');
await import('@formatjs/intl-numberformat/locale-data/xh');
await import('@formatjs/intl-datetimeformat/locale-data/xh');
break;
case 'zu':
await import('@formatjs/intl-relativetimeformat/locale-data/zu');
await import('@formatjs/intl-pluralrules/locale-data/zu');
await import('@formatjs/intl-relativetimeformat/locale-data/zu');
await import('@formatjs/intl-numberformat/locale-data/zu');
await import('@formatjs/intl-datetimeformat/locale-data/zu');
break;
case 'is':
await import('@formatjs/intl-relativetimeformat/locale-data/is');
await import('@formatjs/intl-pluralrules/locale-data/is');
await import('@formatjs/intl-relativetimeformat/locale-data/is');
await import('@formatjs/intl-numberformat/locale-data/is');
await import('@formatjs/intl-datetimeformat/locale-data/is');
break;
case 'it':
await import('@formatjs/intl-relativetimeformat/locale-data/it');
await import('@formatjs/intl-pluralrules/locale-data/it');
await import('@formatjs/intl-relativetimeformat/locale-data/it');
await import('@formatjs/intl-numberformat/locale-data/it');
await import('@formatjs/intl-datetimeformat/locale-data/it');
break;
case 'ka':
await import('@formatjs/intl-relativetimeformat/locale-data/ka');
await import('@formatjs/intl-pluralrules/locale-data/ka');
await import('@formatjs/intl-relativetimeformat/locale-data/ka');
await import('@formatjs/intl-numberformat/locale-data/ka');
await import('@formatjs/intl-datetimeformat/locale-data/ka');
break;
case 'kk':
await import('@formatjs/intl-relativetimeformat/locale-data/kk');
await import('@formatjs/intl-pluralrules/locale-data/kk');
await import('@formatjs/intl-relativetimeformat/locale-data/kk');
await import('@formatjs/intl-numberformat/locale-data/kk');
await import('@formatjs/intl-datetimeformat/locale-data/kk');
break;
case 'qu':
await import('@formatjs/intl-pluralrules/locale-data/en');
await import('@formatjs/intl-relativetimeformat/locale-data/qu');
await import('@formatjs/intl-numberformat/locale-data/qu');
await import('@formatjs/intl-datetimeformat/locale-data/qu');
break;
case 'sw':
await import('@formatjs/intl-relativetimeformat/locale-data/sw');
await import('@formatjs/intl-pluralrules/locale-data/sw');
await import('@formatjs/intl-relativetimeformat/locale-data/sw');
await import('@formatjs/intl-numberformat/locale-data/sw');
await import('@formatjs/intl-datetimeformat/locale-data/sw');
break;
case 'ku':
await import('@formatjs/intl-relativetimeformat/locale-data/ku');
await import('@formatjs/intl-pluralrules/locale-data/ku');
await import('@formatjs/intl-relativetimeformat/locale-data/ku');
await import('@formatjs/intl-numberformat/locale-data/ku');
await import('@formatjs/intl-datetimeformat/locale-data/ku');
break;
case 'ckb':
await import('@formatjs/intl-relativetimeformat/locale-data/ckb');
await import('@formatjs/intl-pluralrules/locale-data/ckb');
await import('@formatjs/intl-relativetimeformat/locale-data/ckb');
await import('@formatjs/intl-numberformat/locale-data/ckb');
await import('@formatjs/intl-datetimeformat/locale-data/ckb');
break;
case 'lv':
await import('@formatjs/intl-relativetimeformat/locale-data/lv');
await import('@formatjs/intl-pluralrules/locale-data/lv');
await import('@formatjs/intl-relativetimeformat/locale-data/lv');
await import('@formatjs/intl-numberformat/locale-data/lv');
await import('@formatjs/intl-datetimeformat/locale-data/lv');
break;
case 'lt':
await import('@formatjs/intl-relativetimeformat/locale-data/lt');
await import('@formatjs/intl-pluralrules/locale-data/lt');
await import('@formatjs/intl-relativetimeformat/locale-data/lt');
await import('@formatjs/intl-numberformat/locale-data/lt');
await import('@formatjs/intl-datetimeformat/locale-data/lt');
break;
case 'hu':
await import('@formatjs/intl-relativetimeformat/locale-data/hu');
await import('@formatjs/intl-pluralrules/locale-data/hu');
await import('@formatjs/intl-relativetimeformat/locale-data/hu');
await import('@formatjs/intl-numberformat/locale-data/hu');
await import('@formatjs/intl-datetimeformat/locale-data/hu');
break;
case 'mi':
await import('@formatjs/intl-relativetimeformat/locale-data/mi');
await import('@formatjs/intl-pluralrules/locale-data/en');
await import('@formatjs/intl-relativetimeformat/locale-data/mi');
await import('@formatjs/intl-numberformat/locale-data/mi');
await import('@formatjs/intl-datetimeformat/locale-data/mi');
break;
case 'mn':
await import('@formatjs/intl-relativetimeformat/locale-data/mn');
await import('@formatjs/intl-pluralrules/locale-data/mn');
await import('@formatjs/intl-relativetimeformat/locale-data/mn');
await import('@formatjs/intl-numberformat/locale-data/mn');
await import('@formatjs/intl-datetimeformat/locale-data/mn');
break;
case 'nl':
await import('@formatjs/intl-relativetimeformat/locale-data/nl');
await import('@formatjs/intl-pluralrules/locale-data/nl');
await import('@formatjs/intl-relativetimeformat/locale-data/nl');
await import('@formatjs/intl-numberformat/locale-data/nl');
await import('@formatjs/intl-datetimeformat/locale-data/nl');
break;
case 'ja':
await import('@formatjs/intl-relativetimeformat/locale-data/ja');
await import('@formatjs/intl-pluralrules/locale-data/ja');
await import('@formatjs/intl-relativetimeformat/locale-data/ja');
await import('@formatjs/intl-numberformat/locale-data/ja');
await import('@formatjs/intl-datetimeformat/locale-data/ja');
break;
case 'nb':
await import('@formatjs/intl-relativetimeformat/locale-data/nb');
await import('@formatjs/intl-pluralrules/locale-data/nb');
await import('@formatjs/intl-relativetimeformat/locale-data/nb');
await import('@formatjs/intl-numberformat/locale-data/nb');
await import('@formatjs/intl-datetimeformat/locale-data/nb');
break;
case 'nn':
await import('@formatjs/intl-relativetimeformat/locale-data/nn');
await import('@formatjs/intl-pluralrules/locale-data/nn');
await import('@formatjs/intl-relativetimeformat/locale-data/nn');
await import('@formatjs/intl-numberformat/locale-data/nn');
await import('@formatjs/intl-datetimeformat/locale-data/nn');
break;
case 'or':
await import('@formatjs/intl-relativetimeformat/locale-data/or');
await import('@formatjs/intl-pluralrules/locale-data/or');
await import('@formatjs/intl-relativetimeformat/locale-data/or');
await import('@formatjs/intl-numberformat/locale-data/or');
await import('@formatjs/intl-datetimeformat/locale-data/or');
break;
case 'uz':
await import('@formatjs/intl-relativetimeformat/locale-data/uz');
await import('@formatjs/intl-pluralrules/locale-data/uz');
await import('@formatjs/intl-relativetimeformat/locale-data/uz');
await import('@formatjs/intl-numberformat/locale-data/uz');
await import('@formatjs/intl-datetimeformat/locale-data/uz');
break;
case 'th':
await import('@formatjs/intl-relativetimeformat/locale-data/th');
await import('@formatjs/intl-pluralrules/locale-data/th');
await import('@formatjs/intl-relativetimeformat/locale-data/th');
await import('@formatjs/intl-numberformat/locale-data/th');
await import('@formatjs/intl-datetimeformat/locale-data/th');
break;
case 'km':
await import('@formatjs/intl-relativetimeformat/locale-data/km');
await import('@formatjs/intl-pluralrules/locale-data/km');
await import('@formatjs/intl-relativetimeformat/locale-data/km');
await import('@formatjs/intl-numberformat/locale-data/km');
await import('@formatjs/intl-datetimeformat/locale-data/km');
break;
case 'pl':
await import('@formatjs/intl-relativetimeformat/locale-data/pl');
await import('@formatjs/intl-pluralrules/locale-data/pl');
await import('@formatjs/intl-relativetimeformat/locale-data/pl');
await import('@formatjs/intl-numberformat/locale-data/pl');
await import('@formatjs/intl-datetimeformat/locale-data/pl');
break;
case 'pt':
await import('@formatjs/intl-relativetimeformat/locale-data/pt');
await import('@formatjs/intl-pluralrules/locale-data/pt');
await import('@formatjs/intl-relativetimeformat/locale-data/pt');
await import('@formatjs/intl-numberformat/locale-data/pt');
await import('@formatjs/intl-datetimeformat/locale-data/pt');
break;
case 'ro':
await import('@formatjs/intl-relativetimeformat/locale-data/ro');
await import('@formatjs/intl-pluralrules/locale-data/ro');
await import('@formatjs/intl-relativetimeformat/locale-data/ro');
await import('@formatjs/intl-numberformat/locale-data/ro');
await import('@formatjs/intl-datetimeformat/locale-data/ro');
break;
case 'ru':
await import('@formatjs/intl-relativetimeformat/locale-data/ru');
await import('@formatjs/intl-pluralrules/locale-data/ru');
await import('@formatjs/intl-relativetimeformat/locale-data/ru');
await import('@formatjs/intl-numberformat/locale-data/ru');
await import('@formatjs/intl-datetimeformat/locale-data/ru');
break;
case 'nso':
await import('@formatjs/intl-relativetimeformat/locale-data/en');
await import('@formatjs/intl-pluralrules/locale-data/nso');
await import('@formatjs/intl-relativetimeformat/locale-data/en');
await import('@formatjs/intl-numberformat/locale-data/en');
await import('@formatjs/intl-datetimeformat/locale-data/en');
break;
case 'tn':
await import('@formatjs/intl-relativetimeformat/locale-data/en');
await import('@formatjs/intl-pluralrules/locale-data/tn');
await import('@formatjs/intl-relativetimeformat/locale-data/en');
await import('@formatjs/intl-numberformat/locale-data/en');
await import('@formatjs/intl-datetimeformat/locale-data/en');
break;
case 'sk':
await import('@formatjs/intl-relativetimeformat/locale-data/sk');
await import('@formatjs/intl-pluralrules/locale-data/sk');
await import('@formatjs/intl-relativetimeformat/locale-data/sk');
await import('@formatjs/intl-numberformat/locale-data/sk');
await import('@formatjs/intl-datetimeformat/locale-data/sk');
break;
case 'sl':
await import('@formatjs/intl-relativetimeformat/locale-data/sl');
await import('@formatjs/intl-pluralrules/locale-data/sl');
await import('@formatjs/intl-relativetimeformat/locale-data/sl');
await import('@formatjs/intl-numberformat/locale-data/sl');
await import('@formatjs/intl-datetimeformat/locale-data/sl');
break;
case 'sr':
await import('@formatjs/intl-relativetimeformat/locale-data/sr');
await import('@formatjs/intl-pluralrules/locale-data/sr');
await import('@formatjs/intl-relativetimeformat/locale-data/sr');
await import('@formatjs/intl-numberformat/locale-data/sr');
await import('@formatjs/intl-datetimeformat/locale-data/sr');
break;
case 'fi':
await import('@formatjs/intl-relativetimeformat/locale-data/fi');
await import('@formatjs/intl-pluralrules/locale-data/fi');
await import('@formatjs/intl-relativetimeformat/locale-data/fi');
await import('@formatjs/intl-numberformat/locale-data/fi');
await import('@formatjs/intl-datetimeformat/locale-data/fi');
break;
case 'sv':
await import('@formatjs/intl-relativetimeformat/locale-data/sv');
await import('@formatjs/intl-pluralrules/locale-data/sv');
await import('@formatjs/intl-relativetimeformat/locale-data/sv');
await import('@formatjs/intl-numberformat/locale-data/sv');
await import('@formatjs/intl-datetimeformat/locale-data/sv');
break;
case 'vi':
await import('@formatjs/intl-relativetimeformat/locale-data/vi');
await import('@formatjs/intl-pluralrules/locale-data/vi');
await import('@formatjs/intl-relativetimeformat/locale-data/vi');
await import('@formatjs/intl-numberformat/locale-data/vi');
await import('@formatjs/intl-datetimeformat/locale-data/vi');
break;
case 'tr':
await import('@formatjs/intl-relativetimeformat/locale-data/tr');
await import('@formatjs/intl-pluralrules/locale-data/tr');
await import('@formatjs/intl-relativetimeformat/locale-data/tr');
await import('@formatjs/intl-numberformat/locale-data/tr');
await import('@formatjs/intl-datetimeformat/locale-data/tr');
break;
case 'uk':
await import('@formatjs/intl-relativetimeformat/locale-data/uk');
await import('@formatjs/intl-pluralrules/locale-data/uk');
await import('@formatjs/intl-relativetimeformat/locale-data/uk');
await import('@formatjs/intl-numberformat/locale-data/uk');
await import('@formatjs/intl-datetimeformat/locale-data/uk');
break;
case 'zh':
await import('@formatjs/intl-relativetimeformat/locale-data/zh');
await import('@formatjs/intl-pluralrules/locale-data/zh');
await import('@formatjs/intl-relativetimeformat/locale-data/zh');
await import('@formatjs/intl-numberformat/locale-data/zh');
await import('@formatjs/intl-datetimeformat/locale-data/zh');
break;
default:
await import('@formatjs/intl-relativetimeformat/locale-data/en');
await import('@formatjs/intl-pluralrules/locale-data/en');
await import('@formatjs/intl-relativetimeformat/locale-data/en');
await import('@formatjs/intl-numberformat/locale-data/en');
await import('@formatjs/intl-datetimeformat/locale-data/en');
break;
}
};

10
src/lib/intl-shape.js Normal file
View file

@ -0,0 +1,10 @@
const PropTypes = require('prop-types');
// intlShape was removed in react-intl@3 and replaced with a TypeScript interface.
// These are some of the commonly used properties from the intl object.
const intlShape = PropTypes.shape({
locale: PropTypes.string.isRequired,
formatMessage: PropTypes.func.isRequired
});
module.exports = intlShape;

View file

@ -1,7 +0,0 @@
const ReactIntl = require('react-intl');
// Add locale data to react intl for all supported languages
const localeData = require('scratch-l10n').localeData;
ReactIntl.addLocaleData(localeData);
module.exports = ReactIntl;

44
src/lib/locales.js Normal file
View file

@ -0,0 +1,44 @@
/**
* Scratch has some locales that are not recognized by Intl. Use an appropriate alternative for these locales.
* @param {string} locale Scratch's locale
* @returns {string} the locale to use in IntlProvider
*/
const scratchLocaleToIntlLocale = locale => {
switch (locale) {
case 'ab':
return 'ru';
case 'an':
case 'rap':
return 'es';
case 'ht':
case 'oc':
return 'fr';
default:
return locale;
}
};
/**
* Gets the locale for the current window.
* @returns {string} locale
*/
const getLocale = () => {
// Get locale from global namespace (see "init.js")
let locale = window._locale || 'en';
if (typeof window._messages !== 'undefined') {
if (typeof window._messages[locale] === 'undefined') {
// Fall back on the split
locale = locale.split('-')[0];
}
if (typeof window._messages[locale] === 'undefined') {
// Language appears to not be supported fall back to 'en'
locale = 'en';
}
}
return locale;
};
module.exports = {
getLocale,
scratchLocaleToIntlLocale
};

View file

@ -2,11 +2,13 @@
const React = require('react'); // eslint-disable-line
const ReactDOM = require('react-dom');
const StoreProvider = require('react-redux').Provider;
const IntlProvider = require('react-intl').IntlProvider;
const IntlProvider = require('./intl.jsx').IntlProvider;
const {getLocale, scratchLocaleToIntlLocale} = require('./locales.js');
const permissionsActions = require('../redux/permissions.js');
const sessionActions = require('../redux/session.js');
const configureStore = require('./configure-store.js');
import intlPolyfill from '../lib/intl-polyfill';
require('../main.scss');
@ -20,38 +22,35 @@ require('../main.scss');
*/
const render = (jsx, element, reducers, initialState, enhancer) => {
// Get locale and messages from global namespace (see "init.js")
let locale = window._locale || 'en';
const locale = getLocale();
let messages = {};
if (typeof window._messages !== 'undefined') {
if (typeof window._messages[locale] === 'undefined') {
// Fall back on the split
locale = locale.split('-')[0];
}
if (typeof window._messages[locale] === 'undefined') {
// Language appears to not be supported fall back to 'en'
locale = 'en';
}
messages = window._messages[locale];
}
const store = configureStore(reducers, initialState, enhancer);
const intlLocale = scratchLocaleToIntlLocale(locale);
// react-intl needs Intl before rendering
intlPolyfill(intlLocale).then(() => {
const store = configureStore(reducers, initialState, enhancer);
// Render view component
ReactDOM.render(
<StoreProvider store={store}>
<IntlProvider
locale={locale}
messages={messages}
>
{jsx}
</IntlProvider>
</StoreProvider>,
element
);
// Render view component
ReactDOM.render(
<StoreProvider store={store}>
<IntlProvider
locale={intlLocale}
messages={messages}
textComponent="span"
>
{jsx}
</IntlProvider>
</StoreProvider>,
element
);
// Get initial session & permissions
store.dispatch(permissionsActions.getPermissions());
store.dispatch(sessionActions.refreshSession());
// Get initial session & permissions
store.dispatch(permissionsActions.getPermissions());
store.dispatch(sessionActions.refreshSession());
});
};
module.exports = render;

82
src/lib/select-unit.js Normal file
View file

@ -0,0 +1,82 @@
const MILLISECONDS_PER_SECOND = 1000;
const SECONDS_PER_MINUTE = 60;
const MINUTES_PER_HOUR = 60;
const HOURS_PER_DAY = 24;
/**
* Determines the best unit (seconds, minutes, hours, days, months, or years)
* and value to represent the time difference between two dates.
* @param {Date} time - date to calculate relative units
* @param {Date} [relativeTo] - optional relative date, defaults to now
* @returns {object} calculated best unit and value
*/
const selectUnit = (time, relativeTo) => {
// Default to now
if (!relativeTo) relativeTo = new Date();
const seconds = (time - relativeTo) / MILLISECONDS_PER_SECOND;
if (Math.abs(seconds) < SECONDS_PER_MINUTE) {
return {
value: Math.trunc(seconds),
unit: 'second'
};
}
const minutes = seconds / SECONDS_PER_MINUTE;
if (Math.abs(minutes) < MINUTES_PER_HOUR) {
return {
value: Math.trunc(minutes),
unit: 'minute'
};
}
const hours = minutes / MINUTES_PER_HOUR;
if (Math.abs(hours) < HOURS_PER_DAY) {
return {
value: Math.trunc(hours),
unit: 'hour'
};
}
const days = hours / HOURS_PER_DAY;
let years = time.getFullYear() - relativeTo.getFullYear();
let months = time.getMonth() - relativeTo.getMonth() + (12 * years);
// Handle calendar months different but less than a complete month elapsed
if (months > 0 && time.getDate() < relativeTo.getDate()) months--;
if (months < 0 && time.getDate() > relativeTo.getDate()) months++;
if (Math.abs(months) < 1) {
return {
value: Math.trunc(days),
unit: 'day'
};
}
// Handle calendar years different but less than a complete year elapsed
if (years > 0 && (time.getMonth() < relativeTo.getMonth() ||
(time.getMonth() === relativeTo.getMonth() && time.getDate() < relativeTo.getDate()))) {
years--;
}
if (years < 0 && (time.getMonth() > relativeTo.getMonth() ||
(time.getMonth() === relativeTo.getMonth() && time.getDate() > relativeTo.getDate()))) {
years++;
}
if (Math.abs(years) < 1) {
return {
value: months,
unit: 'month'
};
}
return {
value: years,
unit: 'year'
};
};
module.exports = {
selectUnit
};

View file

@ -4,8 +4,8 @@ const React = require('react');
const MediaQuery = require('react-responsive').default;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const intlShape = require('../../../lib/intl-shape');
const render = require('../../../lib/render.jsx');
const frameless = require('../../../lib/frameless');

View file

@ -4,8 +4,8 @@ const React = require('react');
const MediaQuery = require('react-responsive').default;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const intlShape = require('../../../lib/intl-shape');
const render = require('../../../lib/render.jsx');
const frameless = require('../../../lib/frameless');
@ -2129,6 +2129,7 @@ class AnnualReport extends React.Component {
comment={this.props.intl.formatMessage(
{id: 'annualReport.2020.communityQuote2Text'}
)}
datetimeCreated="2020-01-01"
/>
</div>
</div>

View file

@ -4,8 +4,8 @@ const React = require('react');
const MediaQuery = require('react-responsive').default;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const intlShape = require('../../../lib/intl-shape');
const render = require('../../../lib/render.jsx');
const frameless = require('../../../lib/frameless');

View file

@ -1,8 +1,9 @@
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const intlShape = require('../../lib/intl-shape');
const Page = require('../../components/page/www/page.jsx');
const render = require('../../lib/render.jsx');

View file

@ -1,7 +1,6 @@
const React = require('react');
const injectIntl = require('react-intl').injectIntl;
const FormattedMessage = require('react-intl').FormattedMessage;
const FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
const render = require('../../lib/render.jsx');
const TitleBanner = require('../../components/title-banner/title-banner.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
@ -10,6 +9,10 @@ const Page = require('../../components/page/www/page.jsx');
require('./camp.scss');
const MainStudio = chunks => <a href="https://scratch.mit.edu/studios/4160302/">{chunks}</a>;
const FinalStudio = chunks => <a href="https://scratch.mit.edu/studios/4160301/">{chunks}</a>;
const CounselorsStudio = chunks => <a href="https://scratch.mit.edu/studios/4160300/">{chunks}</a>;
const Camp = injectIntl(() => (
<div>
<TitleBanner className="masthead mod-blue-bg">
@ -40,7 +43,10 @@ const Camp = injectIntl(() => (
<center>
<h2><FormattedMessage id="camp.welcome" /></h2>
<p id="intro">
<FormattedHTMLMessage id="camp.welcomeIntro" />
<FormattedMessage
id="camp.welcomeIntroHTML"
values={{br: <br />}}
/>
</p>
</center>
<center>
@ -63,7 +69,10 @@ const Camp = injectIntl(() => (
<div className="sidebar column">
<h3><FormattedMessage id="camp.particpateTitle" /></h3>
<p>
<FormattedHTMLMessage id="camp.part1Particpate" />
<FormattedMessage
id="camp.part1ParticpateHTML"
values={{a: MainStudio}}
/>
</p>
</div>
</FlexRow>
@ -86,7 +95,10 @@ const Camp = injectIntl(() => (
<div className="sidebar column">
<h3><FormattedMessage id="camp.particpateTitle" /></h3>
<p>
<FormattedHTMLMessage id="camp.part2Particpate" />
<FormattedMessage
id="camp.part2ParticpateHTML"
values={{a: MainStudio}}
/>
</p>
</div>
</FlexRow>
@ -109,7 +121,10 @@ const Camp = injectIntl(() => (
<div className="sidebar column">
<h3><FormattedMessage id="camp.particpateTitle" /></h3>
<p>
<FormattedHTMLMessage id="camp.part3Particpate" />
<FormattedMessage
id="camp.part3ParticpateHTML"
values={{a: FinalStudio}}
/>
</p>
</div>
</FlexRow>
@ -132,7 +147,10 @@ const Camp = injectIntl(() => (
src="/images/camp/dolphin.svg"
/>
<p>
<FormattedHTMLMessage id="camp.infoCounselors" />
<FormattedMessage
id="camp.infoCounselorsHTML"
values={{a: CounselorsStudio}}
/>
</p>
</div>
<div>

View file

@ -3,19 +3,24 @@
"camp.dates": "July 24th - August 13th",
"camp.welcome": "Welcome to Scratch Camp 2017!",
"camp.welcomeIntro": "Come take a dive into the ocean with us and design your very own creation. Your creation can be anything you might find in the ocean - real or made up! <br /> In this years camp, dive down deep with us in these three parts:",
"camp.welcomeIntroHTML": "Come take a dive into the ocean with us and design your very own creation. Your creation can be anything you might find in the ocean - real or made up!{br}In this years camp, dive down deep with us in these three parts:",
"camp.part1Dates":"Part 1 (July 24th - July 30th)",
"camp.detailsTitle": "Details:",
"camp.part1Details": "Create a project introducing us to a character, real or imagined, that lives in the ocean. You could create a monster from the depths, a cute little starfish, a taco eating shark, or anything else you can imagine.",
"camp.particpateTitle": "How to Participate:",
"camp.part1Particpate": "Part 1 of camp will take place in the <a href=\"https://scratch.mit.edu/studios/4160302/\">Main Camp Cabin studio</a>. Here you can ask questions, view other Scratchers' creations, and submit your own. Go to the studio to learn more!",
"camp.part1ParticpateHTML": "Part 1 of camp will take place in the <a>Main Camp Cabin studio</a>. Here you can ask questions, view other Scratchers' creations, and submit your own. Go to the studio to learn more!",
"camp.part2Dates": "Part 2 (July 31st - August 6th)",
"camp.part2Details": "Now make your character interactive! Does your character have questions to ask us? What happens when you click on it? Does it have any special powers? And more!",
"camp.part2Particpate":"Part 2 of camp will also take place in the <a href=\"https://scratch.mit.edu/studios/4160302/\">Main Camp Cabin studio</a>. Here you can ask questions, view other Scratchers' creations, and submit your own. Go to the studio to learn more!",
"camp.part2ParticpateHTML":"Part 2 of camp will also take place in the <a>Main Camp Cabin studio</a>. Here you can ask questions, view other Scratchers' creations, and submit your own. Go to the studio to learn more!",
"camp.part3Dates": "Part 3 (August 7th - August 13th)",
"camp.part3Details": "Create a project using your own creation along with other Scratchers creations. It could be a game, story, animation, or anything you come up with!",
"camp.part3Particpate":"The <a href=\"https://scratch.mit.edu/studios/4160301/\">Final Projects Camp Cabin studio</a> will hold part 3 of this year's Scratch Camp. Here you can submit your final project, give feedback to others, and celebrate Scratch Camp! Swim on over to the studio when part 3 comes out!",
"camp.part3ParticpateHTML":"The <a>Final Projects Camp Cabin studio</a> will hold part 3 of this year's Scratch Camp. Here you can submit your final project, give feedback to others, and celebrate Scratch Camp! Swim on over to the studio when part 3 comes out!",
"camp.helpfulInfo": "Helpful Information",
"camp.infoCounselors": "The <a href=\"https://scratch.mit.edu/studios/4160300/\">Camp Counselors studio</a> offers a variety of examples for your ocean creation. You can also directly communicate with the Counselors there.",
"camp.infoCounselorsHTML": "The <a>Camp Counselors studio</a> offers a variety of examples for your ocean creation. You can also directly communicate with the Counselors there.",
"camp.infoPart3":"Remember, in part 3, you must use some other creations made for this Scratch Camp. Use their part 2 project to learn about the character's personality!",
"camp.infoTime":"Don't worry if you aren't around the whole time, you can always participate in whatever part you are available for! Just have fun and dive deep!"
}

View file

@ -1,9 +1,9 @@
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
const Page = require('../../components/page/www/page.jsx');
const intlShape = require('../../lib/intl-shape');
const render = require('../../lib/render.jsx');
const HelpForm = require('../../components/helpform/helpform.jsx');

View file

@ -1,12 +1,12 @@
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const bindAll = require('lodash.bindall');
const Page = require('../../components/page/www/page.jsx');
const intlShape = require('../../lib/intl-shape');
const render = require('../../lib/render.jsx');
const detectOS = require('../../lib/detect-os.js').default;
const OS_ENUM = require('../../lib/os-enum.js');

View file

@ -1,7 +1,8 @@
import React, {useState} from 'react';
import {FormattedMessage, injectIntl, intlShape} from 'react-intl';
import {FormattedMessage, injectIntl} from 'react-intl';
import Page from '../../../components/page/www/page.jsx';
const intlShape = require('../../../lib/intl-shape');
import render from '../../../lib/render.jsx';
import FlexRow from '../../../components/flex-row/flex-row.jsx';

View file

@ -1,10 +1,9 @@
const FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
const api = require('../../../lib/api');
const intlShape = require('../../../lib/intl-shape');
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
const SubNavigation = require('../../../components/subnavigation/subnavigation.jsx');
const TitleBanner = require('../../../components/title-banner/title-banner.jsx');
@ -119,7 +118,10 @@ class Download extends React.Component {
<h2 className="installation-column-number-text">{'1'}</h2>
</div>
<h3><FormattedMessage id="download.airTitle" /></h3>
<p><FormattedHTMLMessage id="download.airBody" /></p>
<p><FormattedMessage
id="download.airBodyHTML"
values={{a: chunks => <a href="https://airsdk.harman.com/runtime">{chunks}</a>}}
/></p>
</div>
<div className="installation-column">
<div className="installation-column-number">
@ -208,8 +210,14 @@ class Download extends React.Component {
<section id="other">
<span className="nav-spacer" />
<h2><FormattedMessage id="download.otherVersionsTitle" /></h2>
<p><FormattedHTMLMessage id="download.otherVersionsOlder" /></p>
<p><FormattedHTMLMessage id="download.otherVersionsAdmin" /></p>
<p><FormattedMessage
id="download.otherVersionsOlderHTML"
values={{a: chunks => <a href="http://scratch.mit.edu/scratch_1.4/">{chunks}</a>}}
/></p>
<p><FormattedMessage
id="download.otherVersionsAdminHTML"
values={{a: chunks => <a href="http://llk.github.io/scratch-msi/">{chunks}</a>}}
/></p>
</section>
<section id="issues">
@ -217,7 +225,10 @@ class Download extends React.Component {
<h2><FormattedMessage id="download.knownIssuesTitle" /></h2>
<p><FormattedMessage id="download.knownIssuesOne" /></p>
<p><FormattedMessage id="download.knownIssuesTwo" /></p>
<p><FormattedHTMLMessage id="download.knownIssuesThree" /></p>
<p><FormattedMessage
id="download.knownIssuesThree"
values={{b: chunks => <b>{chunks}</b>}}
/></p>
<p><FormattedMessage id="download.knownIssuesFour" /></p>
<a
className="button mod-link"

View file

@ -4,6 +4,7 @@
"download.installation": "Installation",
"download.airTitle": "Adobe AIR",
"download.airBody": "If you don't already have it, download and install the latest <a href=\"https://airsdk.harman.com/runtime\">Adobe AIR</a>",
"download.airBodyHTML": "If you don't already have it, download and install the latest <a>Adobe AIR</a>",
"download.macOSX": "Mac OS X",
"download.macOlder": "Mac OS 10.5 & Older",
"download.windows": "Windows",
@ -20,7 +21,9 @@
"download.currentVersion": "The current version is {version}.",
"download.otherVersionsTitle": "Other Versions of Scratch",
"download.otherVersionsOlder": "If you have an older computer, or cannot install the Scratch 2.0 offline editor, you can try installing <a href=\"http://scratch.mit.edu/scratch_1.4/\">Scratch 1.4</a>.",
"download.otherVersionsOlderHTML": "If you have an older computer, or cannot install the Scratch 2.0 offline editor, you can try installing <a>Scratch 1.4</a>.",
"download.otherVersionsAdmin": "If you are a network administrator: a Scratch 2.0 MSI has been created and maintained by a member of the community and hosted for public download <a href=\"http://llk.github.io/scratch-msi/\">here</a>.",
"download.otherVersionsAdminHTML": "If you are a network administrator: a Scratch 2.0 MSI has been created and maintained by a member of the community and hosted for public download <a>here</a>.",
"download.knownIssuesTitle": "Known issues",
"download.knownIssuesOne": "If your offline editor is crashing directly after Scratch is opened, install the Scratch 2 offline editor again (see step 2 above). This issue is due to a bug introduced in Adobe AIR version 14 (released April 2014).",
"download.knownIssuesTwo": "Graphic effects blocks (in \"Looks\") may slow down projects due to a known Flash bug.",

View file

@ -1,10 +1,10 @@
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const Page = require('../../components/page/www/page.jsx');
const intlShape = require('../../lib/intl-shape');
const render = require('../../lib/render.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');

View file

@ -1,12 +1,13 @@
const bindAll = require('lodash.bindall');
const classNames = require('classnames');
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const render = require('../../lib/render.jsx');
const api = require('../../lib/api');
const intlShape = require('../../lib/intl-shape');
const {getLocale} = require('../../lib/locales.js');
const Page = require('../../components/page/www/page.jsx');
const Tabs = require('../../components/tabs/tabs.jsx');
@ -79,7 +80,7 @@ class Explore extends React.Component {
handleGetExploreMore () {
const qText = `&q=${this.state.acceptableTabs[this.state.category]}` || '*';
const mode = `&mode=${(this.state.mode ? this.state.mode : 'trending')}`;
const locale = this.props.intl.locale;
const locale = getLocale();
const queryString =
`limit=${this.state.loadNumber}&offset=${this.state.offset}&language=${locale}${mode}${qText}`;

View file

@ -1,9 +1,9 @@
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
const Page = require('../../components/page/www/page.jsx');
const intlShape = require('../../lib/intl-shape');
const render = require('../../lib/render.jsx');
const InformationPage = require('../../components/informationpage/informationpage.jsx');

View file

@ -1,9 +1,9 @@
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const Page = require('../../components/page/www/page.jsx');
const intlShape = require('../../lib/intl-shape');
const render = require('../../lib/render.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');

View file

@ -1,8 +1,6 @@
const bindAll = require('lodash.bindall');
const FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
const Button = require('../../components/forms/button.jsx');
@ -13,6 +11,7 @@ const TTTModal = require('../../components/modal/ttt/modal.jsx');
const TTTTile = require('../../components/ttt-tile/ttt-tile.jsx');
const Page = require('../../components/page/www/page.jsx');
const intlShape = require('../../lib/intl-shape');
const render = require('../../lib/render.jsx');
const Tiles = require('./ttt.json');
@ -99,7 +98,7 @@ class Ideas extends React.Component {
<FormattedMessage id="ideas.gettingStartedTitle" />
</h2>
<p>
<FormattedHTMLMessage id="ideas.gettingStartedText" />
<FormattedMessage id="ideas.gettingStartedText" />
</p>
<a href="/projects/editor/?tutorial=getStarted">
<Button className="ideas-button">
@ -119,7 +118,7 @@ class Ideas extends React.Component {
<FormattedMessage id="ideas.activityGuidesTitle" />
</h2>
<p>
<FormattedHTMLMessage id="ideas.activityGuidesText" />
<FormattedMessage id="ideas.activityGuidesText" />
</p>
</div>
<MasonryGrid >
@ -152,7 +151,7 @@ class Ideas extends React.Component {
<FormattedMessage id="ideas.cardsTitle" />
</h2>
<p>
<FormattedHTMLMessage id="ideas.cardsText" />
<FormattedMessage id="ideas.cardsText" />
</p>
<a
href={this.props.intl.formatMessage({
@ -192,7 +191,7 @@ class Ideas extends React.Component {
<FormattedMessage id="ideas.starterProjectsTitle" />
</h2>
<p>
<FormattedHTMLMessage id="ideas.starterProjectsText" />
<FormattedMessage id="ideas.starterProjectsText" />
</p>
<p>
<a href="/starter_projects">
@ -220,7 +219,10 @@ class Ideas extends React.Component {
<FormattedMessage id="ideas.desktopEditorHeader" />
</h3>
<p>
<FormattedHTMLMessage id="ideas.desktopEditorBody" />
<FormattedMessage
id="ideas.desktopEditorBodyHTML"
values={{a: chunks => <a href="/download">{chunks}</a>}}
/>
</p>
</div>
<div className="tips-info-body mod-narrow">
@ -232,7 +234,13 @@ class Ideas extends React.Component {
<FormattedMessage id="ideas.questionsHeader" />
</h3>
<p>
<FormattedHTMLMessage id="ideas.questionsBody" />
<FormattedMessage
id="ideas.questionsBodyHTML"
values={{
faq: chunks => <a href="/info/faq">{chunks}</a>,
forum: chunks => <a href="/discuss/7/">{chunks}</a>
}}
/>
</p>
</div>
</FlexRow>

View file

@ -29,9 +29,10 @@
"ideas.educatorGuide": "Educator Guide",
"ideas.desktopEditorHeader": "Scratch App Download",
"ideas.desktopEditorBody": "To create projects without an Internet connection, you can <a href=\"/download\">download the Scratch app</a>.",
"ideas.desktopEditorBodyHTML": "To create projects without an Internet connection, you can <a>download the Scratch app</a>.",
"ideas.questionsHeader": "Questions",
"ideas.questionsBody": "Have more questions? See the <a href=\"/info/faq\">Frequently Asked Questions</a> or visit the <a href=\"/discuss/7/\">Help with Scripts Forum</a>.",
"ideas.questionsBodyHTML": "Have more questions? See the <faq>Frequently Asked Questions</faq> or visit the <forum>Help with Scripts Forum</forum>.",
"ideas.cardsPurchase": "Purchase Printed Set",
"ideas.MakeItFlyTitle": "Make It Fly",
"ideas.MakeItFlyDescription": "Choose any character and make it fly!",

View file

@ -3,10 +3,10 @@ const classNames = require('classnames');
const connect = require('react-redux').connect;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const intlShape = require('../../../lib/intl-shape');
const Comment = require('../../../components/comment/comment.jsx');
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
const SocialMessage = require('../../../components/social-message/social-message.jsx');

View file

@ -1,10 +1,10 @@
const classNames = require('classnames');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const intlShape = require('../../../lib/intl-shape');
const SocialMessage = require('../../../components/social-message/social-message.jsx');
const CuratorInviteMessage = props => (

View file

@ -1,10 +1,10 @@
const FormattedDate = require('react-intl').FormattedDate;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const intlShape = require('../../../lib/intl-shape');
const Button = require('../../../components/forms/button.jsx');
const FlexRow = require('../../../components/flex-row/flex-row.jsx');

View file

@ -1,10 +1,10 @@
const classNames = require('classnames');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const intlShape = require('../../../lib/intl-shape');
const SocialMessage = require('../../../components/social-message/social-message.jsx');
const UserJoinMessage = props => (

View file

@ -2,10 +2,10 @@ const bindAll = require('lodash.bindall');
const FormattedMessage = require('react-intl').FormattedMessage;
const FormattedNumber = require('react-intl').FormattedNumber;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const intlShape = require('../../lib/intl-shape');
const Button = require('../../components/forms/button.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');
const Form = require('../../components/forms/form.jsx');

View file

@ -1,10 +1,9 @@
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const Page = require('../../components/page/www/page.jsx');
const intlShape = require('../../lib/intl-shape');
const render = require('../../lib/render.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');

View file

@ -6,11 +6,11 @@ const classNames = require('classnames');
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
const Avatar = require('../../../components/avatar/avatar.jsx');
const EmojiText = require('../../../components/emoji-text/emoji-text.jsx');
const FormattedRelative = require('react-intl').FormattedRelative;
const FormattedMessage = require('react-intl').FormattedMessage;
const ComposeComment = require('./compose-comment.jsx');
const DeleteCommentModal = require('../../../components/modal/comments/delete-comment.jsx');
const ReportCommentModal = require('../../../components/modal/comments/report-comment.jsx');
const RelativeTime = require('../../../components/relative-time/relative-time.jsx');
const decorateText = require('../../../lib/decorate-text.jsx');
require('./comment.scss');
@ -224,7 +224,7 @@ class Comment extends React.Component {
</span>
<FlexRow className="comment-bottom-row">
<span className="comment-time">
<FormattedRelative value={new Date(datetimeCreated)} />
<RelativeTime value={new Date(datetimeCreated)} />
</span>
{(canReply && visible) ? (
<span

View file

@ -3,9 +3,9 @@ const React = require('react');
const bindAll = require('lodash.bindall');
const connect = require('react-redux').connect;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const api = require('../../lib/api');
const intlShape = require('../../lib/intl-shape');
const previewActions = require('../../redux/preview');
class FormsyProjectUpdater extends React.Component {

View file

@ -1,8 +1,8 @@
const injectIntl = require('react-intl').injectIntl;
const PropTypes = require('prop-types');
const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const intlShape = require('../../lib/intl-shape');
const MediaQuery = require('react-responsive').default;
const React = require('react');
const Formsy = require('formsy-react').default;

View file

@ -2,8 +2,9 @@ const React = require('react');
const Page = require('../../components/page/www/page.jsx');
const render = require('../../lib/render.jsx');
const {FormattedMessage, injectIntl, intlShape} = require('react-intl');
const {FormattedMessage, injectIntl} = require('react-intl');
const intlShape = require('../../lib/intl-shape');
const InformationPage = require('../../components/informationpage/informationpage.jsx');
const helpEmailLink = (

View file

@ -1,8 +1,8 @@
const React = require('react');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const intlShape = require('../../lib/intl-shape');
const render = require('../../lib/render.jsx');
const InformationPage = require('../../components/informationpage/informationpage.jsx');

View file

@ -2,11 +2,12 @@ const bindAll = require('lodash.bindall');
const connect = require('react-redux').connect;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
const api = require('../../lib/api');
const intlShape = require('../../lib/intl-shape');
const {getLocale} = require('../../lib/locales.js');
const Button = require('../../components/forms/button.jsx');
const Form = require('../../components/forms/form.jsx');
const Grid = require('../../components/grid/grid.jsx');
@ -126,7 +127,7 @@ class Search extends React.Component {
}
handleGetSearchMore () {
const termText = this.encodeSearchTerm();
const locale = this.props.intl.locale;
const locale = getLocale();
const loadNumber = this.state.loadNumber;
const offset = this.state.offset;
const mode = this.state.mode;

View file

@ -1,12 +1,12 @@
const bindAll = require('lodash.bindall');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const MediaQuery = require('react-responsive').default;
const PropTypes = require('prop-types');
const React = require('react');
const frameless = require('../../lib/frameless');
const intlShape = require('../../lib/intl-shape');
const sessionActions = require('../../redux/session.js');
const shuffle = require('../../lib/shuffle.js').shuffle;
@ -328,7 +328,6 @@ class SplashPresentation extends React.Component { // eslint-disable-line react/
render () {
const featured = this.renderHomepageRows();
const formatHTMLMessage = this.props.intl.formatHTMLMessage;
const formatMessage = this.props.intl.formatMessage;
const messages = {
@ -343,8 +342,8 @@ class SplashPresentation extends React.Component { // eslint-disable-line react/
'intro.forParents': formatMessage({id: 'intro.forParents'}),
'intro.join': formatMessage({id: 'intro.join'}),
'intro.startCreating': formatMessage({id: 'intro.startCreating'}),
'intro.tagLine1': formatHTMLMessage({id: 'intro.tagLine1'}),
'intro.tagLine2': formatHTMLMessage({id: 'intro.tagLine2'}),
'intro.tagLine1': formatMessage({id: 'intro.tagLine1'}),
'intro.tagLine2': formatMessage({id: 'intro.tagLine2'}),
'intro.watchVideo': formatMessage({id: 'intro.watchVideo'}),
'teacherbanner.greeting': formatMessage({id: 'teacherbanner.greeting'}),
'teacherbanner.subgreeting': formatMessage({id: 'teacherbanner.subgreeting'}),

View file

@ -1,9 +1,9 @@
const React = require('react');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const render = require('../../lib/render.jsx');
const intlShape = require('../../lib/intl-shape');
const Page = require('../../components/page/www/page.jsx');
const Carousel = require('../../components/carousel/carousel.jsx');
const Box = require('../../components/box/box.jsx');

View file

@ -3,10 +3,10 @@ const connect = require('react-redux').connect;
const defaults = require('lodash.defaultsdeep');
const PropTypes = require('prop-types');
const React = require('react');
const injectIntl = require('react-intl').injectIntl;
const api = require('../../lib/api');
const injectIntl = require('../../lib/intl.jsx').injectIntl;
const intlShape = require('../../lib/intl.jsx').intlShape;
const intlShape = require('../../lib/intl-shape');
const sessionStatus = require('../../redux/session').Status;
const navigationActions = require('../../redux/navigation.js');

View file

@ -2,10 +2,10 @@ const bindAll = require('lodash.bindall');
const defaults = require('lodash.defaultsdeep');
const PropTypes = require('prop-types');
const React = require('react');
const injectIntl = require('react-intl').injectIntl;
const api = require('../../lib/api');
const injectIntl = require('../../lib/intl.jsx').injectIntl;
const intlShape = require('../../lib/intl.jsx').intlShape;
const intlShape = require('../../lib/intl-shape');
const route = require('../../lib/route');
const Deck = require('../../components/deck/deck.jsx');

View file

@ -2,8 +2,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {FormattedMessage, injectIntl, intlShape} from 'react-intl';
import {FormattedMessage, injectIntl} from 'react-intl';
import intlShape from '../../../lib/intl-shape';
import {selectStudioTitle, selectStudioDescription, selectStudioImage} from '../../../redux/studio';
import Modal from '../../../components/modal/base/modal.jsx';

View file

@ -2,8 +2,9 @@ import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {FormattedMessage} from 'react-intl';
const {injectIntl, intlShape} = require('react-intl');
const {injectIntl} = require('react-intl');
import intlShape from '../../../lib/intl-shape.js';
import ModalInnerContent from '../../../components/modal/base/modal-inner-content.jsx';
import TransferHostTile from './transfer-host-tile.jsx';

View file

@ -3,10 +3,11 @@ import React, {useState} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import classNames from 'classnames';
import {FormattedMessage, intlShape, injectIntl} from 'react-intl';
import {FormattedMessage, injectIntl} from 'react-intl';
import {useAlertContext} from '../../components/alert/alert-context';
import {Errors, inviteCurator} from './lib/studio-member-actions';
import intlShape from '../../lib/intl-shape';
import ValidationMessage from '../../components/forms/validation-message.jsx';
const errorToMessageId = error => {

View file

@ -1,5 +1,6 @@
{
"teacherRegistration.nameStepTitle": "First &amp; Last Name",
"teacherRegistration.nameStepTitleNew": "First & Last Name",
"teacherRegistration.nameStepDescription": "Your name will not be displayed publicly, and will be kept confidential and secure.",
"teacherRegistration.firstName": "First Name",
"teacherRegistration.lastName": "Last Name",

View file

@ -3,10 +3,10 @@ const connect = require('react-redux').connect;
const defaults = require('lodash.defaultsdeep');
const PropTypes = require('prop-types');
const React = require('react');
const injectIntl = require('react-intl').injectIntl;
const api = require('../../lib/api');
const injectIntl = require('../../lib/intl.jsx').injectIntl;
const intlShape = require('../../lib/intl.jsx').intlShape;
const intlShape = require('../../lib/intl-shape');
const sessionActions = require('../../redux/session.js');
const Deck = require('../../components/deck/deck.jsx');

View file

@ -1,14 +1,16 @@
const FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
const Page = require('../../../components/page/www/page.jsx');
const intlShape = require('../../../lib/intl-shape');
const render = require('../../../lib/render.jsx');
const InformationPage = require('../../../components/informationpage/informationpage.jsx');
const RequestFormLink = chunks => <a href="/educators/register">{chunks}</a>;
const ScratchEdLink = chunks => <a href="http://scratched.gse.harvard.edu/">{chunks}</a>;
const TeacherFaq = props => (
<InformationPage title={props.intl.formatMessage({id: 'teacherfaq.title'})}>
<div className="inner info-inner">
@ -47,7 +49,10 @@ const TeacherFaq = props => (
width="565"
/>
<dt><FormattedMessage id="teacherfaq.teacherSignUpTitle" /></dt>
<dd><FormattedHTMLMessage id="teacherfaq.teacherSignUpBody" /></dd>
<dd><FormattedMessage
id="teacherfaq.teacherSignUpBodyHTML"
values={{a: RequestFormLink}}
/></dd>
<dt><FormattedMessage id="teacherfaq.classMultipleTeachersTitle" /></dt>
<dd><FormattedMessage id="teacherfaq.classMultipleTeachersBody" /></dd>
<dt><FormattedMessage id="teacherfaq.teacherPersonalTitle" /></dt>
@ -76,9 +81,12 @@ const TeacherFaq = props => (
/>
</dd>
<dt><FormattedMessage id="teacherfaq.teacherEdTitle" /></dt>
<dd><FormattedHTMLMessage id="teacherfaq.teacherEdBody" /></dd>
<dd><FormattedMessage
id="teacherfaq.teacherEdBodyHTML"
values={{a: ScratchEdLink}}
/></dd>
<dt><FormattedMessage id="teacherfaq.teacherFeaturesTitle" /></dt>
<dd><FormattedHTMLMessage id="teacherfaq.teacherFeaturesBody" /></dd>
<dd><FormattedMessage id="teacherfaq.teacherFeaturesBody" /></dd>
<ul>
<li><FormattedMessage id="teacherfaq.teacherFeaturesConvert" /></li>
<li><FormattedMessage id="teacherfaq.teacherMoveStudents" /></li>
@ -86,7 +94,7 @@ const TeacherFaq = props => (
<li><FormattedMessage id="teacherfaq.teacherLMSs" /></li>
<li><FormattedMessage id="teacherfaq.teacherLimitStudent" /></li>
</ul>
<dd><FormattedHTMLMessage id="teacherfaq.teacherWillNotImplement" /></dd>
<dd><FormattedMessage id="teacherfaq.teacherWillNotImplement" /></dd>
</dl>
</section>
<section id="student-accounts">
@ -187,7 +195,7 @@ const TeacherFaq = props => (
<dd><FormattedMessage id="teacherfaq.commBlockGamesBody1" /></dd>
<dd><FormattedMessage id="teacherfaq.commBlockGamesBody2" /></dd>
<dt><FormattedMessage id="teacherfaq.commInappropriateTitle" /></dt>
<dd><FormattedHTMLMessage id="teacherfaq.commInappropriateBody" /></dd>
<dd><FormattedMessage id="teacherfaq.commInappropriateBody" /></dd>
</dl>
</section>
</div>

View file

@ -8,6 +8,7 @@
"teacherfaq.teacherWhatBody": "A Scratch Teacher Account provides educators with additional features to manage student participation on Scratch, including the ability to create student accounts, organize student projects into studios, and monitor student comments. Learn more about Teacher Accounts in the video below:",
"teacherfaq.teacherSignUpTitle": "How do I request a teacher account?",
"teacherfaq.teacherSignUpBody": "To request a Teacher Account, please go to the teacher account <a href=\"/educators/register\">request form</a>.",
"teacherfaq.teacherSignUpBodyHTML": "To request a Teacher Account, please go to the teacher account <a>request form</a>.",
"teacherfaq.classMultipleTeachersTitle": "Can a class have multiple teachers?",
"teacherfaq.classMultipleTeachersBody": "A class can only have one teacher account associated with it.",
"teacherfaq.teacherPersonalTitle": "Why do you need to know my personal information during registration?",
@ -16,6 +17,7 @@
"teacherfaq.teacherGoogleBody": "No, Scratch does not connect with any classroom management services.",
"teacherfaq.teacherEdTitle": "Are Scratch Teacher accounts linked to ScratchEd accounts?",
"teacherfaq.teacherEdBody": "No, Scratch Teacher accounts are not linked to <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> accounts.",
"teacherfaq.teacherEdBodyHTML": "No, Scratch Teacher accounts are not linked to <a>ScratchEd</a> accounts.",
"teacherfaq.teacherFeaturesTitle": "Does this feature exist, and if not, can you please add it?",
"teacherfaq.teacherFeaturesBody": "Many features are commonly requested, including:",
"teacherfaq.teacherFeaturesConvert": "Converting existing Scratch Accounts into Student Accounts",

View file

@ -1,6 +1,5 @@
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
@ -10,6 +9,7 @@ const TitleBanner = require('../../../components/title-banner/title-banner.jsx')
const Button = require('../../../components/forms/button.jsx');
const Page = require('../../../components/page/www/page.jsx');
const intlShape = require('../../../lib/intl-shape');
const render = require('../../../lib/render.jsx');
require('./landing.scss');
@ -275,7 +275,7 @@ const Landing = props => (
id="teacherlanding.codeClub"
values={{
codeClubLink: (
<a href="https://projects.raspberrypi.org/en/codeclub/scratch-module-1">
<a href="https://projects.raspberrypi.org/en/pathways/scratch-intro">
<FormattedMessage id="teacherlanding.codeClubLink" />
</a>
)

View file

@ -8,6 +8,7 @@
"wedoLegacy.downloadWin": "Download for Windows 10+",
"wedoLegacy.setupTitle": "2. Setup & Help",
"wedoLegacy.setupText": "Connect your WeDo 2.0 by following the steps in the <a href=\"/projects/editor/?tip_bar=ext2\">Tips Window</a>",
"wedoLegacy.setupTextHTML": "Connect your WeDo 2.0 by following the steps in the <a>Tips Window</a>",
"wedoLegacy.createTitle": "3. Create",
"wedoLegacy.createText": "Use the WeDo extension blocks to turn on lights, control motors, and make your project interactive",
"wedoLegacy.wedo2SetupInstructions": "WeDo 2.0 Setup Instructions",

View file

@ -1,4 +1,3 @@
const FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
@ -7,6 +6,8 @@ const render = require('../../lib/render.jsx');
require('./wedo2.scss');
const TipsWindowLink = chunks => <a href="/projects/editor/?tip_bar=ext2">{chunks}</a>;
const Wedo2 = () => (
<div className="wedo">
<div className="top-banner">
@ -40,7 +41,7 @@ const Wedo2 = () => (
<FormattedMessage id="wedoLegacy.installTitle" />
</h4>
<p>
<FormattedHTMLMessage id="wedoLegacy.installText" />
<FormattedMessage id="wedoLegacy.installText" />
<br />
<a href="https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1084869222&mt=12">
<FormattedMessage id="wedoLegacy.downloadMac" />
@ -57,7 +58,10 @@ const Wedo2 = () => (
<FormattedMessage id="wedoLegacy.setupTitle" />
</h4>
<p>
<FormattedHTMLMessage id="wedoLegacy.setupText" />
<FormattedMessage
id="wedoLegacy.setupTextHTML"
values={{a: TipsWindowLink}}
/>
</p>
</div>
<div className="column">

View file

@ -1,9 +1,9 @@
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
const Page = require('../../components/page/www/page.jsx');
const intlShape = require('../../lib/intl-shape');
const render = require('../../lib/render.jsx');
const FlexRow = require('../../components/flex-row/flex-row.jsx');

View file

@ -1,35 +1,50 @@
/*
* Helpers for using enzyme and react-test-renderer with react-intl
* Directly from https://github.com/yahoo/react-intl/wiki/Testing-with-React-Intl
*/
import React from 'react';
import renderer from 'react-test-renderer';
import {IntlProvider, intlShape} from 'react-intl';
import {createIntl, IntlProvider} from 'react-intl';
import {mount, shallow} from 'enzyme';
import intlShape from '../../src/lib/intl-shape';
const intlProvider = new IntlProvider({locale: 'en'}, {});
const {intl} = intlProvider.getChildContext();
const shallowWithIntl = (node, {context} = {}) => {
return shallow(
node,
{
context: Object.assign({}, context),
wrappingComponent: IntlProvider,
wrappingComponentProps: {
locale: 'en',
messages: {}
}
}
).dive();
};
const nodeWithIntlProp = node => React.cloneElement(node, {intl});
const shallowWithIntl = (node, {context} = {}) => shallow(
nodeWithIntlProp(node),
{
context: Object.assign({}, context, {intl})
}
);
const mountWithIntl = (node, {context, childContextTypes} = {}) => mount(
nodeWithIntlProp(node),
{
context: Object.assign({}, context, {intl}),
childContextTypes: Object.assign({}, {intl: intlShape}, childContextTypes)
}
);
const mountWithIntl = (node, {context, childContextTypes} = {}) => {
const intl = createIntl({locale: 'en', messages: {}});
return mount(
node,
{
context: Object.assign({}, context, {intl}),
childContextTypes: Object.assign({}, {intl: intlShape}, childContextTypes),
wrappingComponent: IntlProvider,
wrappingComponentProps: {
locale: 'en',
messages: {}
}
}
);
};
// react-test-renderer component for use with snapshot testing
const componentWithIntl = (children, props = {locale: 'en'}) => renderer.create(
<IntlProvider {...props}>{children}</IntlProvider>
<IntlProvider
textComponent="span"
{...props}
>
{children}
</IntlProvider>
);
export {

View file

@ -1,10 +1,10 @@
const React = require('react');
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
const {shallow} = require('enzyme');
const CommentingStatus = require('../../../src/components/commenting-status/commenting-status.jsx');
describe('CommentingStatus', () => {
test('Basic render', () => {
const component = shallowWithIntl(
const component = shallow(
<CommentingStatus />
);
expect(component.find('div.commenting-status').exists()).toBe(true);
@ -12,7 +12,7 @@ describe('CommentingStatus', () => {
});
test('ClassNames added', () => {
const component = shallowWithIntl(
const component = shallow(
<CommentingStatus
className="class1"
innerClassName="class2"
@ -23,7 +23,7 @@ describe('CommentingStatus', () => {
});
test('Children added', () => {
const component = shallowWithIntl(
const component = shallow(
<CommentingStatus>
<img className="myChildDiv" />
</CommentingStatus>

View file

@ -48,7 +48,7 @@ describe('Compose Comment test', () => {
/>
, {context: {store}}
);
return wrapper.dive(); // unwrap redux connect(injectIntl(ComposeComment))
return wrapper;
};
test('status is EDITING when props do not contain a muteStatus ', () => {

View file

@ -13,7 +13,7 @@ describe('EmailConfirmationBanner', () => {
expect(component.text()).not.toContain('MockEmailConfirmationModal');
const confirmWrapper = component.find({id: 'emailConfirmationBanner.confirm'});
const confirmLink = mountWithIntl(confirmWrapper.instance().props.values.confirmLink);
const confirmLink = mountWithIntl(confirmWrapper.props().values.confirmLink);
confirmLink.simulate('click');
component.update();

View file

@ -39,7 +39,7 @@ describe('Modal', () => {
);
const tipsLinkWrapper = component.find({id: 'emailConfirmationModal.havingTrouble'});
const tipsLink = mountWithIntl(tipsLinkWrapper.instance().props.values.tipsLink);
const tipsLink = mountWithIntl(tipsLinkWrapper.props().values.tipsLink);
tipsLink.simulate('click');
expect(component.text()).toContain('emailConfirmationModal.confirmingTips');
});

View file

@ -9,6 +9,8 @@ jest.mock('../../../src/views/studio/studio-comment.js', () => (
));
describe('Studio comments', () => {
const testComments = [{id: 123, author: {}, datetime_created: new Date().toISOString()}];
test('if there are no comments, they get loaded', () => {
const loadComments = jest.fn();
const component = mountWithIntl(
@ -25,7 +27,7 @@ describe('Studio comments', () => {
// When updated to have comments, load is not called again
loadComments.mockClear();
component.setProps({comments: [{id: 123, author: {}}]});
component.setProps({comments: testComments});
component.update();
expect(loadComments).not.toHaveBeenCalled();
@ -42,7 +44,7 @@ describe('Studio comments', () => {
<StudioComments
hasFetchedSession
isAdmin={false}
comments={[{id: 123, author: {}}]}
comments={testComments}
handleResetComments={resetComments}
/>
);
@ -69,7 +71,7 @@ describe('Studio comments', () => {
<StudioComments
isAdmin
hasFetchedSession
comments={[{id: 123, author: {}}]}
comments={testComments}
handleResetComments={resetComments}
/>
);
@ -81,7 +83,7 @@ describe('Studio comments', () => {
<StudioComments
hasFetchedSession
isAdmin={false}
comments={[{id: 123, author: {}}]}
comments={testComments}
shouldShowCommentsList={false}
/>
);
@ -94,7 +96,7 @@ describe('Studio comments', () => {
<StudioComments
hasFetchedSession
isAdmin={false}
comments={[{id: 123, author: {}}]}
comments={testComments}
shouldShowCommentsList
/>
@ -110,7 +112,7 @@ describe('Studio comments', () => {
<StudioComments
hasFetchedSession
isAdmin={false}
comments={[{id: 123, author: {}}]}
comments={testComments}
shouldShowCommentsList
singleCommentId
/>
@ -129,7 +131,7 @@ describe('Studio comments', () => {
<StudioComments
hasFetchedSession
isAdmin={false}
comments={[{id: 123, author: {}}]}
comments={testComments}
shouldShowCommentsList={false}
singleCommentId
/>
@ -146,7 +148,7 @@ describe('Studio comments', () => {
<StudioComments
hasFetchedSession={false}
isAdmin={false}
comments={[{id: 123, author: {}}]}
comments={testComments}
shouldShowCommentsGloballyOffError
/>
);
@ -159,7 +161,7 @@ describe('Studio comments', () => {
<StudioComments
hasFetchedSession={false}
isAdmin={false}
comments={[{id: 123, author: {}}]}
comments={testComments}
shouldShowCommentsGloballyOffError={false}
/>
);

View file

@ -0,0 +1,63 @@
const {selectUnit} = require('../../../src/lib/select-unit');
describe('unit test lib/select-unit.js', () => {
test('selects seconds', () => {
const result = selectUnit(
new Date('2023-02-03T12:00:00.000Z'),
new Date('2023-02-03T12:00:05.000Z')
);
expect(result.unit).toEqual('second');
expect(result.value).toEqual(-5);
});
test('selects minutes', () => {
const result = selectUnit(
new Date('2023-02-03T12:01:10.000Z'),
new Date('2023-02-03T12:00:00.000Z')
);
expect(result.unit).toEqual('minute');
expect(result.value).toEqual(1);
});
test('selects hours', () => {
const result = selectUnit(
new Date('2023-02-03T12:10:00.000Z'),
new Date('2023-02-03T14:00:00.000Z')
);
expect(result.unit).toEqual('hour');
expect(result.value).toEqual(-1);
});
test('selects days', () => {
const result = selectUnit(
new Date('2023-02-03T12:00:00.000Z'),
new Date('2023-03-02T12:00:00.000Z')
);
expect(result.unit).toEqual('day');
expect(result.value).toEqual(-27);
});
test('selects months', () => {
const result = selectUnit(
new Date('2023-02-03T12:00:00.000Z'),
new Date('2024-02-02T12:00:00.000Z')
);
expect(result.unit).toEqual('month');
expect(result.value).toEqual(-11);
});
test('selects years', () => {
const result = selectUnit(
new Date('2023-02-03T12:00:00.000Z'),
new Date('2025-02-04T12:00:00.000Z')
);
expect(result.unit).toEqual('year');
expect(result.value).toEqual(-2);
});
});