diff --git a/Makefile b/Makefile
index 2f9111b8e..98b2e0527 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,6 @@ GIT_MESSAGE=$(shell git log -1 --pretty=%s 2> /dev/null)
 
 build:
 	@make clean
-	@make translations
 	@make webpack
 	@make tag
 
@@ -55,10 +54,6 @@ test:
 	@make lint
 	@make build
 	@echo ""
-	@make unit
-	@echo ""
-	@make functional
-	@echo ""
 
 lint:
 	$(ESLINT) ./*.js
diff --git a/bin/build-locales b/bin/build-locales
deleted file mode 100755
index 1c32f86ac..000000000
--- a/bin/build-locales
+++ /dev/null
@@ -1,157 +0,0 @@
-#!/usr/bin/env node
-
-/*
-    Converts the existing .po translation files in the module to JSON files.
-    Requires po2json in order to work. Takes as input a directory
-    in which to store the resulting json translation files.
-
-    Takes in as an argument an output directory to put translation files.
-    Searches for files named `l10n.json` in the `src/views/` directory to get
-    template english strings (as well as the general template at `src/l10n.json`).
-
-    It compiles the template strings into a flat object that is compared against the
-    translations in the .po files from the `scratchr2_translations` dependency, using
-    an md5 of the template string without whitespace, and an md5 of the .po msgid string
-    without whitespace.
-
-    The output files are javascript files that declare objects by locale. Each locale
-    has a sub-object with FormattedMessage ids as keys, and translated strings as
-    values. If no translation was found for a string, the default english will be the
-    value.
-
-    Output Example:
-    '''
-    var message = {
-        en: {
-            'general.inAWorld': 'In a world, where bears are invisible...',
-            'general.question': 'Are there bears here?',
-            'general.answer': 'I dunno, but there could be...'
-        },
-        es: {
-            'general.inAWorld': 'En un mundo, donde hay osos invisibles',
-            'general.question': 'Are there bears here?',
-            'general.answer': 'No sé, pero es posible...'
-        }
-    }
-    '''
-*/
-var fs = require('fs');
-var glob = require('glob');
-var merge = require('lodash.merge');
-var path = require('path');
-var po2icu = require('po2icu');
-
-var localeCompare = require('./lib/locale-compare');
-
-// -----------------------------------------------------------------------------
-// Main script
-// -----------------------------------------------------------------------------
-
-
-var args = process.argv.slice(2);
-
-if (!args.length) {
-    process.stdout.write('A destination directory must be specified.');
-    process.exit(1);
-}
-
-var poUiDir = path.resolve(__dirname, '../node_modules/scratchr2_translations/ui');
-var outputDir = path.resolve(__dirname, '../', args[0]);
-try {
-    fs.accessSync(outputDir, fs.F_OK);
-} catch (err) {
-    // Doesn't exist - create it.
-    fs.mkdirSync(outputDir);
-}
-
-// get global locale strings first.
-var globalTemplateFile = path.resolve(__dirname, '../src/l10n.json');
-// message key with english string values (i.e. default values)
-var generalIds = JSON.parse(fs.readFileSync(globalTemplateFile, 'utf8'));
-var viewLocales = {};
-var generalLocales = {
-    en: generalIds
-};
-
-// FormattedMessage id with english string as value. Use for default values in translations
-// Sample structure: { 'general-general.blah': 'blah', 'about-about.blah': 'blahblah' }
-var idsWithICU = {};
-
-// reverse (i.e. english string with message key as the value) object for searching po files.
-// Sample structure: { 'blah': 'general-general.blah', 'blahblah': 'about-about.blah' }
-var icuWithIds = {};
-
-for (var id in generalIds) {
-    idsWithICU['general-' + id] = generalIds[id];
-    icuWithIds[generalIds[id]] = 'general-' + id;
-}
-
-// get view-specific locale strings.
-var files = glob.sync(path.resolve(__dirname, '../src/views/**/l10n.json'));
-files.forEach(function (file) {
-    var dirPath = file.split('/');
-    dirPath.pop();
-    var view = dirPath.pop();
-
-    var viewIds = JSON.parse(fs.readFileSync(file, 'utf8'));
-    viewLocales[view] = {
-        en: viewIds
-    };
-    for (var id in viewIds) {
-        idsWithICU[view + '-' + id] = viewIds[id];
-        icuWithIds[viewIds[id]] = view + '-' + id; // add viewName to identifier for later
-    }
-});
-
-// md5 of english strings with message key as the value for searching po files.
-// Sample structure: { 'sdfas43534sdfasdf': 'general-general.blah', 'lkjfasdf4t342asdfa': 'about-about.blah' }
-var md5WithIds = localeCompare.getMD5Map(icuWithIds);
-
-// Get ui localization strings first
-glob(poUiDir + '/*', function (err, files) {
-    if (err) throw new Error(err);
-    
-    files.forEach(function (file) {
-        var lang = file.split('/').pop();
-        var jsFile = path.resolve(file, 'LC_MESSAGES/djangojs.po');
-        var pyFile = path.resolve(file, 'LC_MESSAGES/django.po');
-
-        var translations = {};
-
-        try {
-            var jsTranslations = po2icu.poFileToICUSync(lang, jsFile);
-            translations = localeCompare.mergeNewTranslations(translations, jsTranslations, idsWithICU, md5WithIds);
-        } catch (err) {
-            process.stdout.write(lang + ': ' + err + '\n');
-        }
-
-        try {
-            var pyTranslations = po2icu.poFileToICUSync(lang, pyFile);
-            translations = localeCompare.mergeNewTranslations(translations, pyTranslations, idsWithICU, md5WithIds);
-        } catch (err) {
-            process.stdout.write(lang + ': ' + err + '\n');
-        }
-
-        // add new translations to locale object
-        for (var id in translations) {
-            var ids = id.split('-'); // [viewName, stringId]
-            var viewName = ids[0];
-            var stringId = ids[1];
-            if (viewLocales.hasOwnProperty(viewName)) {
-                if (!viewLocales[viewName].hasOwnProperty(lang)) viewLocales[viewName][lang] = {};
-                viewLocales[viewName][lang][stringId] = translations[id];
-            } else {
-                // default to general
-                if (!generalLocales.hasOwnProperty(lang)) generalLocales[lang] = {};
-                generalLocales[lang][stringId] = translations[id];
-            }
-        }
-    });
-    
-    for (var view in viewLocales) {
-        var viewTranslations = merge(viewLocales[view], generalLocales);
-        var objectString = JSON.stringify(viewTranslations);
-        var fileString = 'window._messages = ' + objectString + ';';
-        fs.writeFileSync(outputDir + '/' + view + '.js', fileString);
-    }
-});
diff --git a/bin/lib/locale-compare.js b/bin/lib/locale-compare.js
deleted file mode 100644
index 25ac94345..000000000
--- a/bin/lib/locale-compare.js
+++ /dev/null
@@ -1,65 +0,0 @@
-// -----------------------------------------------------------------------------
-// Helper Methods for build-locales node script.
-// -----------------------------------------------------------------------------
-
-var crypto = require('crypto');
-
-var Helpers = {};
-
-/**
- * Get the md5 has of a string with whitespace removed.
- *
- * @param {string} string a string
- * @return {string} an md5 hash of the string in hex.
- */
-Helpers.getMD5 = function (string) {
-    var cleanedString = string.replace(/\s+/g, '');
-    return crypto.createHash('md5').update(cleanedString, 'utf8').digest('hex');
-};
-
-/*
-    Existing translations should be in the key value format specified by react-intl (i.e.
-    formatted message id, with icu string as the value). New Translations should be in the
-    format returned by po2icu (i.e. a source language icu string for key, and a localized
-    language icu string for value).
-
-    ICU Map is an object in the reverse react-intl formatting (icu string as key), which will
-    help determine if the translation belongs in www currently.
-*/
-Helpers.mergeNewTranslations = function (existingTranslations, newTranslations, icuTemplate, md5Map) {
-    for (var id in newTranslations) {
-        var md5 = Helpers.getMD5(id);
-        if (md5Map.hasOwnProperty(md5) && newTranslations[id].length > 0) {
-            existingTranslations[md5Map[md5]] = newTranslations[id];
-        }
-    }
-
-    //Fill in defaults
-    for (var id in icuTemplate) {
-        if (!existingTranslations.hasOwnProperty(id)) existingTranslations[id] = icuTemplate[id];
-    }
-    return existingTranslations;
-};
-
-/**
- * Converts a map of icu strings with react-intl id values into a map
- * with md5 hashes of the icu strings as keys and react-intl id values.
- * This is done so as to eliminate potential po conversion misses that
- * could be caused by different white space formatting between po and icu.
- *
- * The md5 is generated after all white space is removed from the string.
- *
- * @param   {object}    idICUMap    map where key=icuString, value=react-intl id
- *
- * @return  {object}
- */
-Helpers.getMD5Map = function (ICUIdMap) {
-    var md5Map = {};
-    for (var icu in ICUIdMap) {
-        var md5 = Helpers.getMD5(icu);
-        md5Map[md5] = ICUIdMap[icu];
-    }
-    return md5Map;
-};
-
-module.exports = Helpers;
diff --git a/intl-loader.js b/intl-loader.js
new file mode 100644
index 000000000..6fa23473b
--- /dev/null
+++ b/intl-loader.js
@@ -0,0 +1,45 @@
+var glob = require('glob');
+var path = require('path');
+var po2icu = require('po2icu');
+
+var localeCompare = require('./bin/lib/locale-compare');
+
+module.exports = function (source) {
+    this.cacheable();
+
+    var poUiDir = path.resolve(__dirname, './node_modules/scratchr2_translations/ui');
+    var viewIds = JSON.parse(source);
+    var viewLocales = {
+        en: viewIds
+    };
+    var icuWithIds = {};
+    for (var id in viewIds) {
+        icuWithIds[viewIds[id]] = id;
+    }
+    var md5WithIds = localeCompare.getMD5Map(icuWithIds);
+    
+    var files = glob.sync(poUiDir + '/*');
+    files.forEach(function (file) {
+        var lang = file.split('/').pop();
+        var jsFile = path.resolve(file, 'LC_MESSAGES/djangojs.po');
+        var pyFile = path.resolve(file, 'LC_MESSAGES/django.po');
+
+        var translations = {};
+        try {
+            var jsTranslations = po2icu.poFileToICUSync(lang, jsFile);
+            translations = localeCompare.mergeNewTranslations(translations, jsTranslations, viewIds, md5WithIds);
+        } catch (err) {
+            process.stdout.write(lang + ': ' + err + '\n');
+        }
+        try {
+            var pyTranslations = po2icu.poFileToICUSync(lang, pyFile);
+            translations = localeCompare.mergeNewTranslations(translations, pyTranslations, viewIds, md5WithIds);
+        } catch (err) {
+            process.stdout.write(lang + ': ' + err + '\n');
+        }
+
+        viewLocales[lang] = translations;
+    });
+
+    return 'module.exports = ' + JSON.stringify(viewLocales, undefined, '\t') + ';';
+};
diff --git a/package.json b/package.json
index 5ca221023..257840c81 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,6 @@
     "lodash.range": "3.0.1",
     "minilog": "2.0.8",
     "node-sass": "3.3.3",
-    "po2icu": "git://github.com/LLK/po2icu.git#develop",
     "react": "0.14.0",
     "react-addons-test-utils": "0.14.0",
     "react-dom": "0.14.0",
@@ -62,7 +61,7 @@
     "routes-to-nginx-conf": "0.0.4",
     "sass-lint": "1.3.2",
     "sass-loader": "2.0.1",
-    "scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",
+    "scratch-www-intl-loader": "git+ssh://git@github.com/LLK/scratch-www-intl-loader.git#33a0d059308f4fd71c88bbf501e43ce682429ef0",
     "slick-carousel": "1.5.8",
     "source-map-support": "0.3.2",
     "style-loader": "0.12.3",
diff --git a/server/template.html b/server/template.html
index 4741617f0..cd748fbb2 100644
--- a/server/template.html
+++ b/server/template.html
@@ -51,7 +51,6 @@
         <script src="/js/lib/react-intl-with-locales{{min}}.js"></script>
         <script src="/js/lib/raven.min.js"></script>
 
-        <script src="/js/intl/{{view}}.js"></script>
         <script src="/js/{{view}}.bundle.js"></script>
 
         <!-- Error logging (Sentry) -->
diff --git a/src/lib/render.jsx b/src/lib/render.jsx
index 378997e70..29e28015e 100644
--- a/src/lib/render.jsx
+++ b/src/lib/render.jsx
@@ -3,22 +3,21 @@ var ReactDOM = require('react-dom');
 var ReactIntl = require('./intl.jsx');
 var IntlProvider = ReactIntl.IntlProvider;
 
-var render = function (jsx, element) {
+var render = function (jsx, element, messages) {
     // Get locale and messages from global namespace (see "init.js")
     var locale = window._locale || 'en';
-    if (typeof window._messages[locale] === 'undefined') {
+    if (typeof messages[locale] === 'undefined') {
         // Fall back on the split
         locale = locale.split('-')[0];
     }
-    if (typeof window._messages[locale] === 'undefined') {
+    if (typeof messages[locale] === 'undefined') {
         // Language appears to not be supported – fall back to 'en'
         locale = 'en';
     }
-    var messages = window._messages[locale];
 
     // Render component
     var component = ReactDOM.render(
-        <IntlProvider locale={locale} messages={messages}>
+        <IntlProvider locale={locale} messages={messages[locale]}>
             {jsx}
         </IntlProvider>,
         element
diff --git a/src/l10n.json b/src/main.intl
similarity index 100%
rename from src/l10n.json
rename to src/main.intl
diff --git a/src/views/about/l10n.json b/src/views/about/about.intl
similarity index 100%
rename from src/views/about/l10n.json
rename to src/views/about/about.intl
diff --git a/src/views/about/about.jsx b/src/views/about/about.jsx
index 1f178fdb6..fcea2ae61 100644
--- a/src/views/about/about.jsx
+++ b/src/views/about/about.jsx
@@ -1,11 +1,16 @@
-var React = require('react');
 var FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
 var FormattedMessage = require('react-intl').FormattedMessage;
+var merge = require('lodash.merge');
+var React = require('react');
+
 var render = require('../../lib/render.jsx');
 
 require('../../main.scss');
 require('./about.scss');
 
+var generalMessages = require('../../main.intl');
+var viewMessages = require('./about.intl');
+
 var Navigation = require('../../components/navigation/navigation.jsx');
 var Footer = require('../../components/footer/footer.jsx');
 
@@ -104,6 +109,6 @@ var About = React.createClass({
     }
 });
 
-render(<Navigation />, document.getElementById('navigation'));
-render(<Footer />, document.getElementById('footer'));
-render(<About />, document.getElementById('view'));
+render(<Navigation />, document.getElementById('navigation'), generalMessages);
+render(<Footer />, document.getElementById('footer'), generalMessages);
+render(<About />, document.getElementById('view'), merge(generalMessages, viewMessages));
diff --git a/src/views/components/components.jsx b/src/views/components/components.jsx
index 59194eb25..59b06878b 100644
--- a/src/views/components/components.jsx
+++ b/src/views/components/components.jsx
@@ -11,6 +11,8 @@ var Spinner = require('../../components/spinner/spinner.jsx');
 require('../../main.scss');
 require('./components.scss');
 
+var generalMessages = require('../../main.intl');
+
 var Navigation = require('../../components/navigation/navigation.jsx');
 var Footer = require('../../components/footer/footer.jsx');
 
@@ -48,6 +50,6 @@ var Components = React.createClass({
     }
 });
 
-render(<Navigation />, document.getElementById('navigation'));
-render(<Footer />, document.getElementById('footer'));
-render(<Components />, document.getElementById('view'));
+render(<Navigation />, document.getElementById('navigation'), generalMessages);
+render(<Footer />, document.getElementById('footer'), generalMessages);
+render(<Components />, document.getElementById('view'), generalMessages);
diff --git a/src/views/credits/credits.jsx b/src/views/credits/credits.jsx
index 7a948acbd..b3e3a9eff 100644
--- a/src/views/credits/credits.jsx
+++ b/src/views/credits/credits.jsx
@@ -4,6 +4,8 @@ var render = require('../../lib/render.jsx');
 require('../../main.scss');
 require('./credits.scss');
 
+var generalMessages = require('../../main.intl');
+
 var Navigation = require('../../components/navigation/navigation.jsx');
 var Footer = require('../../components/footer/footer.jsx');
 
@@ -298,6 +300,6 @@ var Credits = React.createClass({
     }
 });
 
-render(<Navigation />, document.getElementById('navigation'));
-render(<Footer />, document.getElementById('footer'));
-render(<Credits />, document.getElementById('view'));
+render(<Navigation />, document.getElementById('navigation'), generalMessages);
+render(<Footer />, document.getElementById('footer'), generalMessages);
+render(<Credits />, document.getElementById('view'), generalMessages);
diff --git a/src/views/hoc/l10n.json b/src/views/hoc/hoc.intl
similarity index 100%
rename from src/views/hoc/l10n.json
rename to src/views/hoc/hoc.intl
diff --git a/src/views/hoc/hoc.jsx b/src/views/hoc/hoc.jsx
index c800684c0..e14beac9a 100644
--- a/src/views/hoc/hoc.jsx
+++ b/src/views/hoc/hoc.jsx
@@ -1,6 +1,7 @@
 var classNames = require('classnames');
 var FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
 var FormattedMessage = require('react-intl').FormattedMessage;
+var merge = require('lodash.merge');
 var React = require('react');
 var render = require('../../lib/render.jsx');
 
@@ -11,6 +12,9 @@ var SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
 require('../../main.scss');
 require('./hoc.scss');
 
+var generalMessages = require('../../main.intl');
+var viewMessages = require('./hoc.intl');
+
 var Navigation = require('../../components/navigation/navigation.jsx');
 var Footer = require('../../components/footer/footer.jsx');
 
@@ -409,6 +413,6 @@ var Hoc = React.createClass({
     }
 });
 
-render(<Navigation />, document.getElementById('navigation'));
-render(<Footer />, document.getElementById('footer'));
-render(<Hoc />, document.getElementById('view'));
+render(<Navigation />, document.getElementById('navigation'), generalMessages);
+render(<Footer />, document.getElementById('footer'), generalMessages);
+render(<Hoc />, document.getElementById('view'), merge(generalMessages, viewMessages));
diff --git a/src/views/splash/l10n.json b/src/views/splash/splash.intl
similarity index 100%
rename from src/views/splash/l10n.json
rename to src/views/splash/splash.intl
diff --git a/src/views/splash/splash.jsx b/src/views/splash/splash.jsx
index f2d2b04d7..430a34c73 100644
--- a/src/views/splash/splash.jsx
+++ b/src/views/splash/splash.jsx
@@ -1,4 +1,5 @@
 var injectIntl = require('react-intl').injectIntl;
+var merge = require('lodash.merge');
 var omit = require('lodash.omit');
 var React = require('react');
 var render = require('../../lib/render.jsx');
@@ -20,6 +21,9 @@ var Welcome = require('../../components/welcome/welcome.jsx');
 require('../../main.scss');
 require('./splash.scss');
 
+var generalMessages = require('../../main.intl');
+var viewMessages = require('./splash.intl');
+
 var Navigation = require('../../components/navigation/navigation.jsx');
 var Footer = require('../../components/footer/footer.jsx');
 
@@ -415,6 +419,6 @@ var Splash = injectIntl(React.createClass({
     }
 }));
 
-render(<Navigation />, document.getElementById('navigation'));
-render(<Footer />, document.getElementById('footer'));
-render(<Splash />, document.getElementById('view'));
+render(<Navigation />, document.getElementById('navigation'), generalMessages);
+render(<Footer />, document.getElementById('footer'), generalMessages);
+render(<Splash />, document.getElementById('view'), merge(generalMessages, viewMessages));
diff --git a/test/fixtures/build_locales.json b/test/fixtures/build_locales.json
deleted file mode 100644
index 762540250..000000000
--- a/test/fixtures/build_locales.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-    "<p> <img src=\"{STATIC_URL}/images/help/ask-and-wait.png\" /> asks a question and stores the keyboard input in  <img src=\"{STATIC_URL}/images/help/answer.png\" />. The answer is shared by all sprites. </p><p>If you want to save the current answer, you can store it in a variable or list. For example, <img src=\"{STATIC_URL}/images/help/answer-ex2.png\"/> </p><p>To view the value of answer, click the checkbox next to the answer block.<br><img src=\"{STATIC_URL}/images/help/answer-checkbox.png\" /></p>": "test.id1",
-    "<p> <img src=\"{STATIC_URL}/images/help/ask-and-wait.png\" /> asks a question and stores the keyboard input in  <img src=\"{STATIC_URL}/images/help/answer.png\" />. The question appears in a voice balloon on the screen. The program waits as the user types in a response, until the Enter key is pressed or the check mark is clicked. </p>": "test.id2"
-}
diff --git a/test/fixtures/test_es.po b/test/fixtures/test_es.po
deleted file mode 100644
index 754051c43..000000000
--- a/test/fixtures/test_es.po
+++ /dev/null
@@ -1,72 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-03-20 14:16+0000\n"
-"PO-Revision-Date: 2015-09-21 17:37+0000\n"
-"Last-Translator: Anonymous Pootle User\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"Language: es\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Pootle 2.5.1.1\n"
-"X-POOTLE-MTIME: 1442857052.000000\n"
-
-#: test.html:15
-#, python-format
-msgid ""
-"\n"
-"<p> <img src=\"%(STATIC_URL)s/images/help/ask-and-wait.png\" /> asks a "
-"question and stores the keyboard input in  <img src=\"%(STATIC_URL)s/images/"
-"help/answer.png\" />. The answer is shared by all sprites. </p>\n"
-"<p>If you want to save the current answer, you can store it in a variable or "
-"list. For example, <img src=\"%(STATIC_URL)s/images/help/answer-ex2.png\"/"
-"> \n"
-"</p>\n"
-"\n"
-"<p>\n"
-"To view the value of answer, click the checkbox next to the answer block."
-"<br>\n"
-"<img src=\"%(STATIC_URL)s/images/help/answer-checkbox.png\" />\n"
-"</p>"
-msgstr ""
-"\n"
-"<p><img src=\"%(STATIC_URL)s/images/help/es/ask-and-wait.png\" /> hace una "
-"pregunta y almacena la entrada de teclado en <img src=\"%(STATIC_URL)s/"
-"images/help/es/answer.png\" />. La respuesta se comparte para todos los "
-"objetos. </p>\n"
-"<p>Si deseas guardar la respuesta actual, debes almacenarla en una variable "
-"o una lista. Por ejemplo, <img src=\"%(STATIC_URL)s/images/help/es/answer-"
-"ex2.png\"/> \n"
-"</p>\n"
-"\n"
-"<p>\n"
-"Si deseas ver el valor de una respuesta, haz clic en la casilla que aparece "
-"junto al bloque de respuesta.<br>\n"
-"<img src=\"%(STATIC_URL)s/images/help/es/answer-checkbox.png\" />\n"
-"</p>"
-
-#: test.html:18
-#, python-format
-msgid ""
-"\n"
-"<p> <img src=\"%(STATIC_URL)s/images/help/ask-and-wait.png\" /> asks a "
-"question and stores the keyboard input in  <img src=\"%(STATIC_URL)s/images/"
-"help/answer.png\" />. The question appears in a voice balloon on the screen. "
-"The program waits as the user types in a response, until the Enter key is "
-"pressed or the check mark is clicked. \n"
-"</p>"
-msgstr ""
-"\n"
-"<p> <img src=\"%(STATIC_URL)s/images/help/es/ask-and-wait.png\" /> hace una "
-"pregunta y almacena la entrada de teclado en <img src=\"%(STATIC_URL)s/"
-"images/help/es/answer.png\" />. La pregunta aparece en un globo de voz en la "
-"pantalla. El programa espera hasta que el usuario escriba una respuesta y "
-"presione Enter o haga clic en la casilla de aprobación.\n"
-"</p>"
diff --git a/test/fixtures/test_es_md5map.json b/test/fixtures/test_es_md5map.json
deleted file mode 100644
index b19fcf3ab..000000000
--- a/test/fixtures/test_es_md5map.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-    "2ec20d41b181e1a41c071e13f414a74d": "test.id1",
-    "37ba6d5ef524504215f478912155f9ba": "test.id2"
-}
diff --git a/test/functional/build_locales_complex_strings.js b/test/functional/build_locales_complex_strings.js
deleted file mode 100644
index e9b17c546..000000000
--- a/test/functional/build_locales_complex_strings.js
+++ /dev/null
@@ -1,16 +0,0 @@
-var fs = require('fs');
-var path = require('path');
-var po2icu = require('po2icu');
-var tap = require('tap');
-
-var buildLocales = require('../../bin/lib/locale-compare');
-
-tap.test('buildLocalesFile', function (t) {
-    var md5map = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../fixtures/test_es_md5map.json'), 'utf8'));
-    var newTranslations = po2icu.poFileToICUSync('es', path.resolve(__dirname, '../fixtures/test_es.po'));
-    var translations = buildLocales.mergeNewTranslations({}, newTranslations, {}, md5map);
-
-    t.ok(translations['test.id1'] !== undefined);
-    t.ok(translations['test.id2'] !== undefined);
-    t.end();
-});
diff --git a/test/functional/build_locales_md5map.js b/test/functional/build_locales_md5map.js
deleted file mode 100644
index 98d99dc6f..000000000
--- a/test/functional/build_locales_md5map.js
+++ /dev/null
@@ -1,16 +0,0 @@
-var fs = require('fs');
-var path = require('path');
-var tap = require('tap');
-
-var buildLocales = require('../../bin/lib/locale-compare');
-
-tap.test('buildLocalesFile', function (t) {
-    var actualMd5map = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../fixtures/test_es_md5map.json'), 'utf8'));
-    var templates = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../fixtures/build_locales.json'), 'utf8'));
-    var testMd5map = buildLocales.getMD5Map(templates);
-
-    for (var key in actualMd5map) {
-        t.ok(testMd5map[key] !== undefined);
-    }
-    t.end();
-});
diff --git a/test/functional/build_locales_mergeTranslations.js b/test/functional/build_locales_mergeTranslations.js
deleted file mode 100644
index 47d6e512d..000000000
--- a/test/functional/build_locales_mergeTranslations.js
+++ /dev/null
@@ -1,23 +0,0 @@
-var tap = require('tap');
-
-var buildLocales = require('../../bin/lib/locale-compare');
-
-tap.test('buildLocalesMergeTranslations', function (t) {
-    var existingTranslations = {
-        'test.test1': 'It\'s like raaayaaain, on your wedding day',
-        'test.test2': 'Free to flyyy, when you already paid'
-    };
-    var newTranslations = {
-        'Isn\'t it ironic? No.': 'Es irónico? No.'
-    };
-    var md5map = {
-        'c21ce5ceefe167028182032d4255a384': 'test.test1',
-        '9c40648034e467e16f8d6ae24bd610ab': 'test.test2',
-        '6885a345adafb3a9dd43d9f549430c88': 'test.test3'
-    };
-
-    var mergedTranslations = buildLocales.mergeNewTranslations(existingTranslations, newTranslations, {}, md5map);
-    t.ok(mergedTranslations['test.test3'] !== undefined);
-    t.ok(mergedTranslations['test.test2'] !== undefined);
-    t.end();
-});
diff --git a/test/unit/build_locales_getmd5.js b/test/unit/build_locales_getmd5.js
deleted file mode 100644
index fd9b7ff06..000000000
--- a/test/unit/build_locales_getmd5.js
+++ /dev/null
@@ -1,11 +0,0 @@
-var tap = require('tap');
-
-var buildLocales = require('../../bin/lib/locale-compare');
-
-tap.test('buildLocalesGetMD5', function (t) {
-    var testString1 = 'are there bears here?';
-    var testString2 = 'are\nthere\tbears here?';
-    
-    t.equal(buildLocales.getMD5(testString1), buildLocales.getMD5(testString2));
-    t.end();
-});
diff --git a/webpack.config.js b/webpack.config.js
index d793e2c9b..367309dc3 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -44,6 +44,10 @@ module.exports = {
             {
                 test: /\.(png|jpg|gif|eot|svg|ttf|woff)$/,
                 loader: 'url-loader'
+            },
+            {
+                test: /\.intl$/,
+                loader: 'scratch-www-intl-loader'
             }
         ]
     },
@@ -52,8 +56,7 @@ module.exports = {
     },
     plugins: [
         new CopyWebpackPlugin([
-            {from: 'static'},
-            {from: 'intl', to: 'js/intl'}
+            {from: 'static'}
         ]),
         new webpack.optimize.UglifyJsPlugin({
             compress: {