From 683241cac12e98f1df6f59b1c46bd05bb9c41f73 Mon Sep 17 00:00:00 2001
From: chrisgarrity <chrisg@media.mit.edu>
Date: Thu, 14 Dec 2017 15:53:18 -0500
Subject: [PATCH] Localize extension blocks (#968)

Localizes the pen extension:
- loads all pen locale information - may want to consider dynamically loading locales later.
---
 package.json              |  2 +-
 src/containers/blocks.jsx | 19 ++++++++++++++++++-
 src/reducers/intl.js      |  3 ++-
 test/integration/test.js  | 20 ++++++++++++++++++++
 4 files changed, 41 insertions(+), 3 deletions(-)

diff --git a/package.json b/package.json
index d3fff0c95..34d4d64c4 100644
--- a/package.json
+++ b/package.json
@@ -85,7 +85,7 @@
     "rimraf": "^2.6.1",
     "scratch-audio": "latest",
     "scratch-blocks": "latest",
-    "scratch-l10n": "^2.0.0",
+    "scratch-l10n": "latest",
     "scratch-paint": "latest",
     "scratch-render": "latest",
     "scratch-storage": "^0.3.0",
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx
index 895c1af58..647f1f8ba 100644
--- a/src/containers/blocks.jsx
+++ b/src/containers/blocks.jsx
@@ -43,6 +43,7 @@ class Blocks extends React.Component {
             'onBlockGlowOn',
             'onBlockGlowOff',
             'handleExtensionAdded',
+            'handleBlocksInfoUpdate',
             'onTargetsUpdate',
             'onVisualReport',
             'onWorkspaceUpdate',
@@ -72,6 +73,7 @@ class Blocks extends React.Component {
         addFunctionListener(this.workspace, 'zoom', this.onWorkspaceMetricsChange);
 
         this.attachVM();
+        this.props.vm.setLocale(this.props.locale, this.props.messages);
     }
     shouldComponentUpdate (nextProps, nextState) {
         return (
@@ -79,10 +81,15 @@ class Blocks extends React.Component {
             this.props.isVisible !== nextProps.isVisible ||
             this.props.toolboxXML !== nextProps.toolboxXML ||
             this.props.extensionLibraryVisible !== nextProps.extensionLibraryVisible ||
-            this.props.customProceduresVisible !== nextProps.customProceduresVisible
+            this.props.customProceduresVisible !== nextProps.customProceduresVisible ||
+            this.props.locale !== nextProps.locale
         );
     }
     componentDidUpdate (prevProps) {
+        if (prevProps.locale !== this.props.locale) {
+            this.props.vm.setLocale(this.props.locale, this.props.messages);
+        }
+
         if (prevProps.toolboxXML !== this.props.toolboxXML) {
             const selectedCategoryName = this.workspace.toolbox_.getSelectedItem().name_;
             this.workspace.updateToolbox(this.props.toolboxXML);
@@ -120,6 +127,7 @@ class Blocks extends React.Component {
         this.props.vm.addListener('workspaceUpdate', this.onWorkspaceUpdate);
         this.props.vm.addListener('targetsUpdate', this.onTargetsUpdate);
         this.props.vm.addListener('EXTENSION_ADDED', this.handleExtensionAdded);
+        this.props.vm.addListener('BLOCKSINFO_UPDATE', this.handleBlocksInfoUpdate);
     }
     detachVM () {
         this.props.vm.removeListener('SCRIPT_GLOW_ON', this.onScriptGlowOn);
@@ -130,6 +138,7 @@ class Blocks extends React.Component {
         this.props.vm.removeListener('workspaceUpdate', this.onWorkspaceUpdate);
         this.props.vm.removeListener('targetsUpdate', this.onTargetsUpdate);
         this.props.vm.removeListener('EXTENSION_ADDED', this.handleExtensionAdded);
+        this.props.vm.removeListener('BLOCKSINFO_UPDATE', this.handleBlocksInfoUpdate);
     }
     updateToolboxBlockValue (id, value) {
         const block = this.workspace
@@ -212,6 +221,10 @@ class Blocks extends React.Component {
         const toolboxXML = makeToolboxXML(target.isStage, target.id, dynamicBlocksXML);
         this.props.updateToolboxState(toolboxXML);
     }
+    handleBlocksInfoUpdate (blocksInfo) {
+        // @todo Later we should replace this to avoid all the warnings from redefining blocks.
+        this.handleExtensionAdded(blocksInfo);
+    }
     handleCategorySelected (categoryName) {
         this.workspace.toolbox_.setSelectedCategoryByName(categoryName);
     }
@@ -288,6 +301,8 @@ Blocks.propTypes = {
     customProceduresVisible: PropTypes.bool,
     extensionLibraryVisible: PropTypes.bool,
     isVisible: PropTypes.bool,
+    locale: PropTypes.string,
+    messages: PropTypes.objectOf(PropTypes.string),
     onActivateColorPicker: PropTypes.func,
     onActivateCustomProcedures: PropTypes.func,
     onRequestCloseCustomProcedures: PropTypes.func,
@@ -351,6 +366,8 @@ Blocks.defaultProps = {
 
 const mapStateToProps = state => ({
     extensionLibraryVisible: state.modals.extensionLibrary,
+    locale: state.intl.locale,
+    messages: state.intl.messages,
     toolboxXML: state.toolbox.toolboxXML,
     customProceduresVisible: state.customProcedures.active
 });
diff --git a/src/reducers/intl.js b/src/reducers/intl.js
index 12841c407..15a2d307b 100644
--- a/src/reducers/intl.js
+++ b/src/reducers/intl.js
@@ -6,8 +6,9 @@ import defaultsDeep from 'lodash.defaultsdeep';
 import localeData from 'scratch-l10n';
 import guiMessages from 'scratch-l10n/locales/gui-msgs';
 import paintMessages from 'scratch-l10n/locales/paint-msgs';
+import penMessages from 'scratch-l10n/locales/pen-msgs';
 
-const combinedMessages = defaultsDeep({}, guiMessages.messages, paintMessages.messages);
+const combinedMessages = defaultsDeep({}, guiMessages.messages, paintMessages.messages, penMessages.messages);
 
 Object.keys(localeData).forEach(locale => {
     // TODO: will need to handle locales not in the default intl - see www/custom-locales
diff --git a/test/integration/test.js b/test/integration/test.js
index d6baaf094..ebfb3fbbd 100644
--- a/test/integration/test.js
+++ b/test/integration/test.js
@@ -219,4 +219,24 @@ describe('costumes, sounds and variables', () => {
         const logs = await getLogs(errorWhitelist);
         await expect(logs).toEqual([]);
     });
+
+    test('Localization', async () => {
+        await loadUri(uri);
+        await clickText('Blocks');
+        await clickText('Extensions');
+        await clickText('Pen', modalScope); // Modal closes
+        await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
+        await clickText('English');
+        await clickText('Deutsch');
+        await new Promise(resolve => setTimeout(resolve, 1000)); // wait for blocks refresh
+        await clickText('Pen'); // will need to be updated when 'Pen' is translated
+
+        // Make sure "Add Sprite" has changed to "Figur hinzufügen"
+        await findByText('Figur hinzufügen');
+        // Find the stamp block in German
+        await findByText('Abdruck');
+
+        const logs = await getLogs(errorWhitelist);
+        await expect(logs).toEqual([]);
+    });
 });
-- 
GitLab