diff --git a/README.md b/README.md
index c036a170930285319aff1fe3f5b348883df9ec65..4b5b91ea8c4d429d11f1e87b0f49af2d88ccf874 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,21 @@ npm start
 ```
 Then go to [http://localhost:8601/](http://localhost:8601/) - the playground outputs the default GUI component
 
+## Developing alongside other Scratch repositories
+
+If you wish to develop scratch-gui alongside other scratch repositories that depend on it, you may wish
+to have the other repositories use your local scratch-gui build instead of fetching the current production
+version of the scratch-gui that is found by default using `npm install`.
+
+To do this:
+
+1. Make sure you have run `npm install` from this repository's top level
+2. Make sure you have run `npm install` from the top level of each repository (such as scratch-www) that depends on scratch-gui
+3. From this repository's top level, build the `dist` directory by running `BUILD_MODE=dist npm run build`
+4. From this repository's top level, establish a link to this repository by running `npm link`
+5. From the top level of each repository that depends on scratch-gui, run `npm link scratch-gui`
+6. Build or run the repositories that depend on scratch-gui
+
 ## Testing
 NOTE: If you're a windows user, please run these scripts in Windows `cmd.exe`  instead of Git Bash/MINGW64.
 
diff --git a/package.json b/package.json
index f1ddf3c3ef546c10342d1c6a3d6b6d4367a2e5a0..c43fb30c03283d3ec1519601f6d88823dae46c46 100644
--- a/package.json
+++ b/package.json
@@ -96,13 +96,13 @@
     "redux-throttle": "0.1.1",
     "rimraf": "^2.6.1",
     "scratch-audio": "0.1.0-prerelease.20180625202813",
-    "scratch-blocks": "0.1.0-prerelease.1534513944",
-    "scratch-l10n": "3.0.20180817135616",
-    "scratch-paint": "0.2.0-prerelease.20180816205442",
-    "scratch-render": "0.1.0-prerelease.20180811013828",
-    "scratch-storage": "0.5.1",
+    "scratch-blocks": "0.1.0-prerelease.1535116879",
+    "scratch-l10n": "3.0.20180824134256",
+    "scratch-paint": "0.2.0-prerelease.20180823231354",
+    "scratch-render": "0.1.0-prerelease.20180824141819",
+    "scratch-storage": "1.0.0",
     "scratch-svg-renderer": "0.2.0-prerelease.20180817005452",
-    "scratch-vm": "0.2.0-prerelease.20180816213529",
+    "scratch-vm": "0.2.0-prerelease.20180824135031",
     "selenium-webdriver": "3.6.0",
     "startaudiocontext": "1.2.1",
     "style-loader": "^0.22.1",
diff --git a/src/components/asset-panel/asset-panel.css b/src/components/asset-panel/asset-panel.css
index 9a999057b57a29f12b8e288365605a814b5864f1..bfd578909fdf2a8efd826bba4899fd7e8240a4bd 100644
--- a/src/components/asset-panel/asset-panel.css
+++ b/src/components/asset-panel/asset-panel.css
@@ -5,16 +5,32 @@
     display: flex;
     flex-grow: 1;
     border: 1px solid $ui-black-transparent;
-    border-top-right-radius: $space;
     background: white;
     font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
     font-size: 0.85rem;
 }
 
+[dir="ltr"] .wrapper {
+    border-top-right-radius: $space;
+    border-bottom-right-radius: $space;
+}
+
+[dir="rtl"] .wrapper {
+    border-top-left-radius: $space;
+    border-bottom-left-radius: $space;
+}
+
 .detail-area {
     display: flex;
     flex-grow: 1;
     flex-shrink: 1;
-    border-left: 1px solid $ui-black-transparent;
     overflow-y: auto;
 }
+
+[dir="ltr"] .detail-area {
+    border-left: 1px solid $ui-black-transparent;
+}
+
+[dir="rtl"] .detail-area {
+    border-right: 1px solid $ui-black-transparent;
+}
diff --git a/src/components/backpack/backpack.css b/src/components/backpack/backpack.css
index 9143844d0973b35b457092b66132ed6135256d07..33585ca90de66225f89228aa5aa9a35f6798adca 100644
--- a/src/components/backpack/backpack.css
+++ b/src/components/backpack/backpack.css
@@ -9,7 +9,6 @@
 .backpack-header {
     margin-top: 0.5rem;
     border: 1px solid $ui-black-transparent;
-    border-top-right-radius: $space;
     background: $ui-white;
     padding: 0.25rem;
     text-align: center;
@@ -20,6 +19,14 @@
     user-select: none;
 }
 
+[dir="ltr"] .backpack-header {
+    border-top-right-radius: $space;
+}
+
+[dir="rtl"] .backpack-header {
+    border-top-left-radius: $space;
+}
+
 .backpack-list {
     position: relative;
     display: flex;
diff --git a/src/components/blocks/blocks.css b/src/components/blocks/blocks.css
index e02486ce0fe376d091090b4355d2a0febc3c8331..3d87405893c9268041744d1d5d2aecfa4bd4afc6 100644
--- a/src/components/blocks/blocks.css
+++ b/src/components/blocks/blocks.css
@@ -12,6 +12,13 @@
     border-bottom-right-radius: $space;
 }
 
+[dir="rtl"] .blocks :global(.injectionDiv) {
+    border-top-right-radius: 0;
+    border-bottom-right-radius: 0;
+    border-top-left-radius: $space;
+    border-bottom-left-radius: $space;
+}
+
 .blocks :global(.blocklyMainBackground) {
     stroke: none;
 }
@@ -31,6 +38,11 @@
     -ms-overflow-style: none;
 }
 
+[dir="rtl"] .blocks :global(.blocklyToolboxDiv) {
+    border-right: none;
+    border-left: 1px solid $ui-black-transparent;
+}
+
 .blocks :global(.blocklyToolboxDiv::-webkit-scrollbar) {
     display: none;
 }
@@ -40,6 +52,12 @@
     box-sizing: content-box;
 }
 
+[dir="rtl"] .blocks :global(.blocklyFlyout) {
+    border-right: none;
+    border-left: 1px solid $ui-black-transparent;
+}
+
+
 .blocks :global(.blocklyBlockDragSurface) {
     /*
         Fix an issue where the drag surface was preventing hover events for sharing blocks.
diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css
index 46cf41d2b79baf011980c08bef45b07eac1e9611..757653e9ac32d18507cf2f8d2d433a2ecf8562e8 100644
--- a/src/components/gui/gui.css
+++ b/src/components/gui/gui.css
@@ -130,6 +130,10 @@
 
 [dir="rtl"] .tab img {
     margin-left: 0.125rem;
+}
+
+/* only mirror blocks tab icon */
+[dir="rtl"] .tab:nth-of-type(1) img {
     transform: scaleX(-1);
 }
 
diff --git a/src/components/import-modal/import-modal.jsx b/src/components/import-modal/import-modal.jsx
index 1028d5ddc20f0490a19bfecbf82526869a5a2d27..60b130212281496375a7066f9c87aa4543104336 100644
--- a/src/components/import-modal/import-modal.jsx
+++ b/src/components/import-modal/import-modal.jsx
@@ -156,6 +156,7 @@ ImportModal.propTypes = {
     hasValidationError: PropTypes.bool.isRequired,
     inputValue: PropTypes.string.isRequired,
     intl: intlShape.isRequired,
+    isRtl: PropTypes.bool,
     onCancel: PropTypes.func.isRequired,
     onChange: PropTypes.func.isRequired,
     onGoBack: PropTypes.func.isRequired,
diff --git a/src/components/language-selector/language-selector.css b/src/components/language-selector/language-selector.css
index d8ae588ae13fd05fc9d6b07eb6316de7427a4613..25b9951608e138250d158906fcd56833a3790348 100644
--- a/src/components/language-selector/language-selector.css
+++ b/src/components/language-selector/language-selector.css
@@ -5,19 +5,27 @@
     height:  1.5rem;
 }
 
-.disabled {
-    opacity: .5;
-}
-
+/* Position the language select over the language icon, and make it transparent */
 .language-select {
-    margin: .5rem;
-    height: 1.85rem;
-    border: 1px solid $motion-primary;
+    position: absolute;
+    width: $language-selector-width;
+    height: $menu-bar-height;
+    opacity: 0;
     user-select: none;
-    outline: none;
-    background: rgba(255, 255, 255, 0.5);
-    color: $motion-tertiary;
     font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+    font-size: .875rem;
+}
+
+[dir="ltr"] .language-select {
+    right: 0;
+}
+
+[dir="rtl"] .language-select {
+    left: 0;
+}
+
+.language-select option {
+    opacity: 1;
 }
 
 .language-select:focus {
diff --git a/src/components/language-selector/language-selector.jsx b/src/components/language-selector/language-selector.jsx
index a6707bbced246129ee8d464694b6a117ec833555..fd4caef5f93590ddcd5878920ca4c084d292545c 100644
--- a/src/components/language-selector/language-selector.jsx
+++ b/src/components/language-selector/language-selector.jsx
@@ -1,58 +1,38 @@
 import PropTypes from 'prop-types';
 import React from 'react';
 
-import Box from '../box/box.jsx';
 import locales from 'scratch-l10n';
 import styles from './language-selector.css';
 
 // supported languages to exclude from the menu, but allow as a URL option
 const ignore = ['he'];
 
-class LanguageSelector extends React.Component {
-    render () {
-        const {
-            componentRef,
-            currentLocale,
-            onChange,
-            ...componentProps
-        } = this.props;
-        return (
-            <Box
-                {...componentProps}
-            >
-                <div
-                    className={styles.group}
-                    ref={componentRef}
-                >
-                    <select
-                        className={styles.languageSelect}
-                        value={currentLocale}
-                        onChange={onChange}
+const LanguageSelector = ({currentLocale, label, onChange}) => (
+    <select
+        aria-label={label}
+        className={styles.languageSelect}
+        value={currentLocale}
+        onChange={onChange}
+    >
+        {
+            Object.keys(locales)
+                .filter(l => !ignore.includes(l))
+                .map(locale => (
+                    <option
+                        key={locale}
+                        value={locale}
                     >
-                        {
-                            Object.keys(locales)
-                                .filter(l => !ignore.includes(l))
-                                .map(locale => (
-                                    <option
-                                        key={locale}
-                                        value={locale}
-                                    >
-                                        {locales[locale].name}
-                                    </option>
-                                ))
-                        }
-                    </select>
-                </div>
-            </Box>
-        );
-    }
-}
+                        {locales[locale].name}
+                    </option>
+                ))
+        }
+    </select>
+);
 
 LanguageSelector.propTypes = {
-    componentRef: PropTypes.func,
     currentLocale: PropTypes.string,
-    onChange: PropTypes.func,
-    open: PropTypes.bool
+    label: PropTypes.string,
+    onChange: PropTypes.func
 };
 
 export default LanguageSelector;
diff --git a/src/components/library-item/library-item.css b/src/components/library-item/library-item.css
index 3258b4a7bd16162f239d73990a26df77894f2ea0..fd7217c6744937c184d5e2115a4dcdc284b0a7a9 100644
--- a/src/components/library-item/library-item.css
+++ b/src/components/library-item/library-item.css
@@ -99,7 +99,6 @@
 
 .coming-soon-text {
     position: absolute;
-    transform: translate(calc(2 * $space), calc(2 * $space));
     background-color: $data-primary;
     border-radius: 1rem;
     box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25);
@@ -108,3 +107,11 @@
     font-weight: bold;
     color: $ui-white;
 }
+
+[dir="ltr"] .coming-soon-text {
+    transform: translate(calc(2 * $space), calc(2 * $space));
+}
+
+[dir="rtl"] .coming-soon-text {
+    transform: translate(calc(-2 * $space), calc(2 * $space));
+}
diff --git a/src/components/menu-bar/menu-bar.css b/src/components/menu-bar/menu-bar.css
index c1ae3f9fa2144059547841170b555535081062f8..de2854c5006508276a78718b87d337301e162094 100644
--- a/src/components/menu-bar/menu-bar.css
+++ b/src/components/menu-bar/menu-bar.css
@@ -48,8 +48,13 @@
     height:  1.5rem;
 }
 
+.language-caret {
+    margin-bottom: .625rem;
+}
+
 .language-menu {
     display: inline-flex;
+    width: $language-selector-width;
 }
 
 .menu {
diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx
index 11562411086b5cb47d97300f424d6494a1fd7000..5eb4ad3312d5a27d333b128ff70739c1eaae6c6b 100644
--- a/src/components/menu-bar/menu-bar.jsx
+++ b/src/components/menu-bar/menu-bar.jsx
@@ -137,7 +137,8 @@ class MenuBar extends React.Component {
         super(props);
         bindAll(this, [
             'handleLanguageMouseUp',
-            'handleRestoreOption'
+            'handleRestoreOption',
+            'restoreOptionMessage'
         ]);
     }
     handleLanguageMouseUp (e) {
@@ -151,6 +152,35 @@ class MenuBar extends React.Component {
             this.props.onRequestCloseEdit();
         };
     }
+    restoreOptionMessage (deletedItem) {
+        switch (deletedItem) {
+        case 'Sprite':
+            return (<FormattedMessage
+                defaultMessage="Restore Sprite"
+                description="Menu bar item for restoring the last deleted sprite."
+                id="gui.menuBar.restoreSprite"
+            />);
+        case 'Sound':
+            return (<FormattedMessage
+                defaultMessage="Restore Sound"
+                description="Menu bar item for restoring the last deleted sound."
+                id="gui.menuBar.restoreSound"
+            />);
+        case 'Costume':
+            return (<FormattedMessage
+                defaultMessage="Restore Costume"
+                description="Menu bar item for restoring the last deleted costume."
+                id="gui.menuBar.restoreCostume"
+            />);
+        default: {
+            return (<FormattedMessage
+                defaultMessage="Restore"
+                description="Menu bar item for restoring the last deleted item in its disabled state." /* eslint-disable-line max-len */
+                id="gui.menuBar.restore"
+            />);
+        }
+        }
+    }
     render () {
         return (
             <Box className={styles.menuBar}>
@@ -165,39 +195,19 @@ class MenuBar extends React.Component {
                             />
                         </div>
                         <div
-                            className={classNames(styles.menuBarItem, styles.hoverable, {
-                                [styles.active]: this.props.languageMenuOpen
-                            })}
-                            onMouseUp={this.handleLanguageMouseUp}
+                            className={classNames(styles.menuBarItem, styles.hoverable, styles.languageMenu)}
                         >
-                            {/* @TODO: remove coming soon tooltip wrapper  https://github.com/LLK/scratch-gui/issues/2664  */}
-                            <MenuBarItemTooltip
-                                enable
-                                id="menubar-selector"
-                                place="right"
-                            >
-                                <div
-                                    aria-label={this.props.intl.formatMessage(ariaMessages.language)}
-                                    className={classNames(styles.languageMenu)}
-                                >
-                                    <img
-                                        className={styles.languageIcon}
-                                        src={languageIcon}
-                                    />
-                                    <img
-                                        className={styles.dropdownCaret}
-                                        src={dropdownCaret}
-                                    />
-                                </div>
-                                <MenuBarMenu
-                                    open={this.props.languageMenuOpen}
-                                    place={this.props.isRtl ? 'left' : 'right'}
-                                    onRequestClose={this.props.onRequestCloseLanguage}
-                                >
-                                    <LanguageSelector />
-                                </MenuBarMenu>
-
-                            </MenuBarItemTooltip>
+                            <div>
+                                <img
+                                    className={styles.languageIcon}
+                                    src={languageIcon}
+                                />
+                                <img
+                                    className={styles.languageCaret}
+                                    src={dropdownCaret}
+                                />
+                            </div>
+                            <LanguageSelector label={this.props.intl.formatMessage(ariaMessages.language)} />
                         </div>
                         <div
                             className={classNames(styles.menuBarItem, styles.hoverable, {
@@ -306,18 +316,7 @@ class MenuBar extends React.Component {
                                         className={classNames({[styles.disabled]: !restorable})}
                                         onClick={this.handleRestoreOption(handleRestore)}
                                     >
-                                        {deletedItem === 'Sprite' ?
-                                            <FormattedMessage
-                                                defaultMessage="Restore Sprite"
-                                                description="Menu bar item for restoring the last deleted sprite."
-                                                id="gui.menuBar.restoreSprite"
-                                            /> :
-                                            <FormattedMessage
-                                                defaultMessage="Restore"
-                                                description="Menu bar item for restoring the last deleted item in its disabled state." /* eslint-disable-line max-len */
-                                                id="gui.menuBar.restore"
-                                            />
-                                        }
+                                        {this.restoreOptionMessage(deletedItem)}
                                     </MenuItem>
                                 )}</DeletionRestorer>
                                 <MenuSection>
diff --git a/src/components/prompt/prompt.css b/src/components/prompt/prompt.css
index ae523e188013ad9c79433ed908f4623665551a50..911271f1997f4d2e37b5a12f8a5b18eda0fea582 100644
--- a/src/components/prompt/prompt.css
+++ b/src/components/prompt/prompt.css
@@ -88,8 +88,15 @@
 .more-options-icon {
     width: .75rem;
     height: .75rem;
-    margin-left: .5rem;
     vertical-align: middle;
     padding-bottom: .2rem;
     opacity: .5;
 }
+
+[dir="ltr"] .more-options-icon {
+    margin-left: .5rem;
+}
+
+[dir="rtl"] .more-options-icon {
+    margin-right: .5rem;
+}
diff --git a/src/components/stage-header/stage-header.css b/src/components/stage-header/stage-header.css
index dd2dc2c65ca642338358d681f572a1b04796fd14..3e6f59c75ac245d74441f709acfdbb1689017ca2 100644
--- a/src/components/stage-header/stage-header.css
+++ b/src/components/stage-header/stage-header.css
@@ -59,6 +59,10 @@
     height: 100%;
 }
 
+[dir="rtl"] .stage-button-icon {
+    transform: scaleX(-1);
+}
+
 [dir="ltr"] .stage-button-first {
     border-top-right-radius: 0;
     border-bottom-right-radius: 0;
diff --git a/src/components/target-pane/target-pane.css b/src/components/target-pane/target-pane.css
index 404283d19fd8c77bafc1e9f0b307628168d1cfc4..07b07cb161a62ebd9f61f1a158115a80076a3471 100644
--- a/src/components/target-pane/target-pane.css
+++ b/src/components/target-pane/target-pane.css
@@ -11,5 +11,12 @@
     display: flex;
     flex-basis: 72px;
     flex-shrink: 0;
+}
+
+[dir="ltr"] .stage-selector-wrapper {
     margin-left: calc($space / 2);
 }
+
+[dir="rtl"] .stage-selector-wrapper {
+    margin-right: calc($space / 2);
+}
diff --git a/src/containers/controls.jsx b/src/containers/controls.jsx
index 7cc52870fea7dad1e58b168d406019a4a4549162..2869a047a6ba5722762d86f32875419cd14cfb0b 100644
--- a/src/containers/controls.jsx
+++ b/src/containers/controls.jsx
@@ -64,5 +64,7 @@ const mapStateToProps = state => ({
     projectRunning: state.scratchGui.vmStatus.running,
     turbo: state.scratchGui.vmStatus.turbo
 });
+// no-op function to prevent dispatch prop being passed to component
+const mapDispatchToProps = () => ({});
 
-export default connect(mapStateToProps)(Controls);
+export default connect(mapStateToProps, mapDispatchToProps)(Controls);
diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx
index 454c5ffcef7a203ac7a927bb4b6873cb4b0041f1..4c63a8cceb7bec2c86632886b6e195ff1c4535ae 100644
--- a/src/containers/costume-tab.jsx
+++ b/src/containers/costume-tab.jsx
@@ -24,6 +24,8 @@ import {
     SOUNDS_TAB_INDEX
 } from '../reducers/editor-tab';
 
+import {setRestore} from '../reducers/restore-deletion';
+
 import addLibraryBackdropIcon from '../components/asset-panel/icon--add-backdrop-lib.svg';
 import addLibraryCostumeIcon from '../components/asset-panel/icon--add-costume-lib.svg';
 import fileUploadIcon from '../components/action-menu/icon--file-upload.svg';
@@ -135,7 +137,11 @@ class CostumeTab extends React.Component {
         this.setState({selectedCostumeIndex: costumeIndex});
     }
     handleDeleteCostume (costumeIndex) {
-        this.props.vm.deleteCostume(costumeIndex);
+        const restoreCostumeFun = this.props.vm.deleteCostume(costumeIndex);
+        this.props.dispatchUpdateRestore({
+            restoreFun: restoreCostumeFun,
+            deletedItem: 'Costume'
+        });
     }
     handleDuplicateCostume (costumeIndex) {
         this.props.vm.duplicateCostume(costumeIndex);
@@ -232,6 +238,7 @@ class CostumeTab extends React.Component {
     }
     render () {
         const {
+            dispatchUpdateRestore, // eslint-disable-line no-unused-vars
             intl,
             onNewCostumeFromCameraClick,
             onNewLibraryBackdropClick,
@@ -325,6 +332,7 @@ class CostumeTab extends React.Component {
 
 CostumeTab.propTypes = {
     cameraModalVisible: PropTypes.bool,
+    dispatchUpdateRestore: PropTypes.func,
     editingTarget: PropTypes.string,
     intl: intlShape,
     onActivateSoundsTab: PropTypes.func.isRequired,
@@ -372,6 +380,9 @@ const mapDispatchToProps = dispatch => ({
     },
     onRequestCloseCameraModal: () => {
         dispatch(closeCameraCapture());
+    },
+    dispatchUpdateRestore: restoreState => {
+        dispatch(setRestore(restoreState));
     }
 });
 
diff --git a/src/containers/error-boundary.jsx b/src/containers/error-boundary.jsx
index fbb15f3b998d1deb80cb309146c1dfa48d88a593..6485a41b84b7cf227ec4e54e496f2afbe0d88c2f 100644
--- a/src/containers/error-boundary.jsx
+++ b/src/containers/error-boundary.jsx
@@ -78,6 +78,6 @@ const mapStateToProps = state => ({
 });
 
 // no-op function to prevent dispatch prop being passed to component
-const mapDispatchToProps = () => {};
+const mapDispatchToProps = () => ({});
 
 export default connect(mapStateToProps, mapDispatchToProps)(ErrorBoundary);
diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index cdcb87c8985b31498c8e5eee61c54067225901fe..33d8fe58b247c28a3760529ecdbaefcd31190599 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -94,6 +94,7 @@ class GUI extends React.Component {
 }
 
 GUI.propTypes = {
+    assetHost: PropTypes.string,
     children: PropTypes.node,
     fetchingProject: PropTypes.bool,
     importInfoVisible: PropTypes.bool,
@@ -101,6 +102,7 @@ GUI.propTypes = {
     onSeeCommunity: PropTypes.func,
     previewInfoVisible: PropTypes.bool,
     projectData: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
+    projectHost: PropTypes.string,
     vm: PropTypes.instanceOf(VM)
 };
 
diff --git a/src/containers/paint-editor-wrapper.jsx b/src/containers/paint-editor-wrapper.jsx
index 390f6ef598b7ca63f52868529ba9720a0f652d24..dc14578bd6112b8081a98025d6089aa7da5631c9 100644
--- a/src/containers/paint-editor-wrapper.jsx
+++ b/src/containers/paint-editor-wrapper.jsx
@@ -56,6 +56,7 @@ PaintEditorWrapper.propTypes = {
     name: PropTypes.string,
     rotationCenterX: PropTypes.number,
     rotationCenterY: PropTypes.number,
+    rtl: PropTypes.bool,
     selectedCostumeIndex: PropTypes.number.isRequired,
     vm: PropTypes.instanceOf(VM)
 };
@@ -74,6 +75,7 @@ const mapStateToProps = (state, {selectedCostumeIndex}) => {
         imageFormat: costume && costume.dataFormat,
         imageId: targetId && `${targetId}${costume.skinId}`,
         image: state.scratchGui.vm.getCostume(index),
+        rtl: state.locales.isRtl,
         vm: state.scratchGui.vm
     };
 };
diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx
index 2245f3d2f6acda3304d8de506cfde811cd0ece16..a30b8f97e82f93fe1407cc55271f81273feef3f1 100644
--- a/src/containers/sound-tab.jsx
+++ b/src/containers/sound-tab.jsx
@@ -34,6 +34,8 @@ import {
     COSTUMES_TAB_INDEX
 } from '../reducers/editor-tab';
 
+import {setRestore} from '../reducers/restore-deletion';
+
 class SoundTab extends React.Component {
     constructor (props) {
         super(props);
@@ -76,10 +78,11 @@ class SoundTab extends React.Component {
     }
 
     handleDeleteSound (soundIndex) {
-        this.props.vm.deleteSound(soundIndex);
+        const restoreFun = this.props.vm.deleteSound(soundIndex);
         if (soundIndex >= this.state.selectedSoundIndex) {
             this.setState({selectedSoundIndex: Math.max(0, soundIndex - 1)});
         }
+        this.props.dispatchUpdateRestore({restoreFun, deletedItem: 'Sound'});
     }
 
     handleDuplicateSound (soundIndex) {
@@ -153,6 +156,7 @@ class SoundTab extends React.Component {
 
     render () {
         const {
+            dispatchUpdateRestore, // eslint-disable-line no-unused-vars
             intl,
             vm,
             onNewSoundFromLibraryClick,
@@ -252,6 +256,7 @@ class SoundTab extends React.Component {
 }
 
 SoundTab.propTypes = {
+    dispatchUpdateRestore: PropTypes.func,
     editingTarget: PropTypes.string,
     intl: intlShape,
     onActivateCostumesTab: PropTypes.func.isRequired,
@@ -294,6 +299,9 @@ const mapDispatchToProps = dispatch => ({
     },
     onRequestCloseSoundLibrary: () => {
         dispatch(closeSoundLibrary());
+    },
+    dispatchUpdateRestore: restoreState => {
+        dispatch(setRestore(restoreState));
     }
 });
 
diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx
index a0197ad6dd9ecfc09e61c5a783878395eef7e509..01e617c8e77385244507977b037c7f31a918a26d 100644
--- a/src/containers/sprite-selector-item.jsx
+++ b/src/containers/sprite-selector-item.jsx
@@ -2,28 +2,24 @@ import bindAll from 'lodash.bindall';
 import PropTypes from 'prop-types';
 import React from 'react';
 import {connect} from 'react-redux';
-import {defineMessages, injectIntl, intlShape} from 'react-intl';
 
 import {setHoveredSprite} from '../reducers/hovered-target';
 import {updateAssetDrag} from '../reducers/asset-drag';
 import {getEventXY} from '../lib/touch-utils';
+import VM from 'scratch-vm';
+import {SVGRenderer} from 'scratch-svg-renderer';
 
 import SpriteSelectorItemComponent from '../components/sprite-selector-item/sprite-selector-item.jsx';
 
 const dragThreshold = 3; // Same as the block drag threshold
-
-const messages = defineMessages({
-    deleteSpriteConfirmation: {
-        defaultMessage: 'Are you sure you want to delete this?',
-        description: 'Confirmation for deleting sprites',
-        id: 'gui.spriteSelectorItem.deleteSpriteConfirmation'
-    }
-});
+// Contains 'font-family', but doesn't only contain 'font-family="none"'
+const HAS_FONT_REGEXP = 'font-family(?!="none")';
 
 class SpriteSelectorItem extends React.Component {
     constructor (props) {
         super(props);
         bindAll(this, [
+            'getCostumeUrl',
             'handleClick',
             'handleDelete',
             'handleDuplicate',
@@ -34,6 +30,34 @@ class SpriteSelectorItem extends React.Component {
             'handleMouseMove',
             'handleMouseUp'
         ]);
+        this.svgRenderer = new SVGRenderer();
+        // Asset ID of the SVG currently in SVGRenderer
+        this.svgRendererAssetId = null;
+    }
+    getCostumeUrl () {
+        if (this.props.costumeURL) return this.props.costumeURL;
+        if (!this.props.assetId) return null;
+
+        const storage = this.props.vm.runtime.storage;
+        const asset = storage.get(this.props.assetId);
+        // If the SVG refers to fonts, they must be inlined in order to display correctly in the img tag.
+        // Avoid parsing the SVG when possible, since it's expensive.
+        if (asset.assetType === storage.AssetType.ImageVector) {
+            // If the asset ID has not changed, no need to re-parse
+            if (this.svgRendererAssetId === this.props.assetId) {
+                return this.cachedUrl;
+            }
+
+            const svgString = this.props.vm.runtime.storage.get(this.props.assetId).decodeText();
+            if (svgString.match(HAS_FONT_REGEXP)) {
+                this.svgRendererAssetId = this.props.assetId;
+                this.svgRenderer.loadString(svgString);
+                const svgText = this.svgRenderer.toString(true /* shouldInjectFonts */);
+                this.cachedUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;
+                return this.cachedUrl;
+            }
+        }
+        return this.props.vm.runtime.storage.get(this.props.assetId).encodeDataURI();
     }
     handleMouseUp () {
         this.initialOffset = null;
@@ -58,7 +82,7 @@ class SpriteSelectorItem extends React.Component {
         const dy = currentOffset.y - this.initialOffset.y;
         if (Math.sqrt((dx * dx) + (dy * dy)) > dragThreshold) {
             this.props.onDrag({
-                img: this.props.costumeURL,
+                img: this.getCostumeUrl(),
                 currentOffset: currentOffset,
                 dragging: true,
                 dragType: this.props.dragType,
@@ -84,10 +108,7 @@ class SpriteSelectorItem extends React.Component {
     }
     handleDelete (e) {
         e.stopPropagation(); // To prevent from bubbling back to handleClick
-        // eslint-disable-next-line no-alert
-        if (window.confirm(this.props.intl.formatMessage(messages.deleteSpriteConfirmation))) {
-            this.props.onDeleteButtonClick(this.props.id);
-        }
+        this.props.onDeleteButtonClick(this.props.id);
     }
     handleDuplicate (e) {
         e.stopPropagation(); // To prevent from bubbling back to handleClick
@@ -115,11 +136,14 @@ class SpriteSelectorItem extends React.Component {
             onExportButtonClick,
             dragPayload,
             receivedBlocks,
+            costumeURL,
+            vm,
             /* eslint-enable no-unused-vars */
             ...props
         } = this.props;
         return (
             <SpriteSelectorItemComponent
+                costumeURL={this.getCostumeUrl()}
                 onClick={this.handleClick}
                 onDeleteButtonClick={onDeleteButtonClick ? this.handleDelete : null}
                 onDuplicateButtonClick={onDuplicateButtonClick ? this.handleDuplicate : null}
@@ -144,7 +168,6 @@ SpriteSelectorItem.propTypes = {
     dragType: PropTypes.string,
     id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
     index: PropTypes.number,
-    intl: intlShape.isRequired,
     name: PropTypes.string,
     onClick: PropTypes.func,
     onDeleteButtonClick: PropTypes.func,
@@ -152,14 +175,15 @@ SpriteSelectorItem.propTypes = {
     onDuplicateButtonClick: PropTypes.func,
     onExportButtonClick: PropTypes.func,
     receivedBlocks: PropTypes.bool.isRequired,
-    selected: PropTypes.bool
+    selected: PropTypes.bool,
+    vm: PropTypes.instanceOf(VM).isRequired
 };
 
-const mapStateToProps = (state, {assetId, costumeURL, id}) => ({
-    costumeURL: costumeURL || (assetId && state.scratchGui.vm.runtime.storage.get(assetId).encodeDataURI()),
+const mapStateToProps = (state, {id}) => ({
     dragging: state.scratchGui.assetDrag.dragging,
     receivedBlocks: state.scratchGui.hoveredTarget.receivedBlocks &&
-            state.scratchGui.hoveredTarget.sprite === id
+            state.scratchGui.hoveredTarget.sprite === id,
+    vm: state.scratchGui.vm
 });
 const mapDispatchToProps = dispatch => ({
     dispatchSetHoveredSprite: spriteId => {
@@ -168,8 +192,12 @@ const mapDispatchToProps = dispatch => ({
     onDrag: data => dispatch(updateAssetDrag(data))
 });
 
-
-export default connect(
+const ConnectedComponent = connect(
     mapStateToProps,
     mapDispatchToProps
-)(injectIntl(SpriteSelectorItem));
+)(SpriteSelectorItem);
+
+export {
+    ConnectedComponent as default,
+    HAS_FONT_REGEXP // Exposed for testing
+};
diff --git a/src/css/units.css b/src/css/units.css
index 59d9a2e7cdd244f7bb3beaf8482adce58a6a3380..db51ed00934d08ee8d01204bab3f65353afd5905 100644
--- a/src/css/units.css
+++ b/src/css/units.css
@@ -6,6 +6,7 @@ $space: 0.5rem;
 $sprites-per-row: 5;
 
 $menu-bar-height: 3rem;
+$language-selector-width: 3rem;
 $sprite-info-height: 6rem;
 $stage-menu-height: 2.75rem;
 
diff --git a/src/lib/libraries/extensions/index.jsx b/src/lib/libraries/extensions/index.jsx
index 0bd3b36ee9a2dd9285139ca554a8ca564e9cf358..edd4fa512b6e2e414762aa6fccb9fdab5cf7dfdf 100644
--- a/src/lib/libraries/extensions/index.jsx
+++ b/src/lib/libraries/extensions/index.jsx
@@ -158,7 +158,7 @@ export default [
             />
         ),
         featured: true,
-        disabled: true,
+        disabled: false,
         launchDeviceConnectionFlow: true,
         useAutoScan: true,
         deviceImage: wedoDeviceImage,
diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx
index b2256b82a4ace77d8d2bec3a426bb02852cd3464..67e70f90019e90b92c262fdef5067c12c9c927ff 100644
--- a/src/lib/vm-listener-hoc.jsx
+++ b/src/lib/vm-listener-hoc.jsx
@@ -89,6 +89,10 @@ const vmListenerHOC = function (WrappedComponent) {
                 onKeyUp,
                 onMonitorsUpdate,
                 onTargetsUpdate,
+                onProjectRunStart,
+                onProjectRunStop,
+                onTurboModeOff,
+                onTurboModeOn,
                 /* eslint-enable no-unused-vars */
                 ...props
             } = this.props;
diff --git a/test/integration/localization.test.js b/test/integration/localization.test.js
index abd8ee4676db49ad620e6aae480aff6cefbea363..cf02aa86b1bc2c9707950fbb7317506302c836c5 100644
--- a/test/integration/localization.test.js
+++ b/test/integration/localization.test.js
@@ -26,7 +26,6 @@ describe('Localization', () => {
         await loadUri(uri);
         await clickXpath('//button[@title="Try It"]');
         await clickXpath('//*[@aria-label="language selector"]');
-        await clickText('English');
         await clickText('Deutsch');
         await new Promise(resolve => setTimeout(resolve, 1000)); // wait for blocks refresh
 
diff --git a/test/integration/sounds.test.js b/test/integration/sounds.test.js
index 7c306a5eee2c9c05c2d16d1c9ed8a1a489f5370c..ca1a3d275d8271e63af5068659429d4b4a4cee7e 100644
--- a/test/integration/sounds.test.js
+++ b/test/integration/sounds.test.js
@@ -33,8 +33,6 @@ describe('Working with sounds', () => {
         // Delete the sound
         await rightClickText('Meow', scope.soundsTab);
         await clickText('delete', scope.soundsTab);
-        await driver.switchTo().alert()
-            .accept();
 
         // Add it back
         await clickXpath('//button[@aria-label="Choose a Sound"]');
diff --git a/test/integration/sprites.test.js b/test/integration/sprites.test.js
index 41f2eec3ab4220e83fc08f8936c35328367f3da4..5b99efcb62e0496312d31f15e8746b260ad204a1 100644
--- a/test/integration/sprites.test.js
+++ b/test/integration/sprites.test.js
@@ -55,8 +55,6 @@ describe('Working with sprites', () => {
         await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
         await rightClickText('Sprite1', scope.spriteTile);
         await clickText('delete', scope.spriteTile);
-        await driver.switchTo().alert()
-            .accept();
         // Confirm that the stage has been switched to
         await findByText('Stage selected: no motion blocks');
         const logs = await getLogs();
diff --git a/test/unit/containers/sprite-selector-item.test.jsx b/test/unit/containers/sprite-selector-item.test.jsx
index 85032603d4b3a1712db3fb431a96d109dc40d7d7..25200a1fb5f8376bd7ade0d9b13ff98eaf990885 100644
--- a/test/unit/containers/sprite-selector-item.test.jsx
+++ b/test/unit/containers/sprite-selector-item.test.jsx
@@ -4,6 +4,7 @@ import configureStore from 'redux-mock-store';
 import {Provider} from 'react-redux';
 
 import SpriteSelectorItem from '../../../src/containers/sprite-selector-item';
+import {HAS_FONT_REGEXP} from '../../../src/containers/sprite-selector-item';
 import CloseButton from '../../../src/components/close-button/close-button';
 
 describe('SpriteSelectorItem Container', () => {
@@ -48,22 +49,19 @@ describe('SpriteSelectorItem Container', () => {
         onDeleteButtonClick = jest.fn();
         dispatchSetHoveredSprite = jest.fn();
         selected = true;
-        // Mock window.confirm() which is called when the close button is clicked.
-        global.confirm = jest.fn(() => true);
     });
 
-    test('should confirm if the user really wants to delete the sprite', () => {
+    test('should delete the sprite', () => {
         const wrapper = mountWithIntl(getContainer());
         wrapper.find(CloseButton).simulate('click');
-        expect(global.confirm).toHaveBeenCalled();
         expect(onDeleteButtonClick).toHaveBeenCalledWith(1337);
     });
 
-    test('should not delete the sprite if the user cancels', () => {
-        global.confirm = jest.fn(() => false);
-        const wrapper = mountWithIntl(getContainer());
-        wrapper.find(CloseButton).simulate('click');
-        expect(global.confirm).toHaveBeenCalled();
-        expect(onDeleteButtonClick).not.toHaveBeenCalled();
+    test('Has font regexp works', () => {
+        expect('font-family="Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy();
+        expect('font-family="none" font-family="Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy();
+        expect('font-family = "Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy();
+
+        expect('font-family="none"'.match(HAS_FONT_REGEXP)).toBeFalsy();
     });
 });
diff --git a/webpack.config.js b/webpack.config.js
index a44d90c48e246e1f45c450a665dbb025028b8b83..1013bb87a0857718222800cf06807fafc92f0271 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -177,7 +177,7 @@ module.exports = [
         ])
     })
 ].concat(
-    process.env.NODE_ENV === 'production' ? (
+    process.env.NODE_ENV === 'production' || process.env.BUILD_MODE === 'dist' ? (
         // export as library
         defaultsDeep({}, base, {
             target: 'web',