diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000000000000000000000000000000000000..194df44f15a4d9f16223c6348baf25b8ca9757e9
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,8 @@
+{
+    "plugins": [
+        "transform-object-rest-spread",
+        ["react-intl", {
+            "messagesDir": "./translations/messages/"
+        }]],
+    "presets": ["es2015", "react"]
+}
diff --git a/.gitignore b/.gitignore
index 5915ce40461f1679ad355d6ca80823b61509cf41..6861cfdb016b2540f8814060a72119b7e3bb1cb8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,6 @@ npm-*
 # Build
 /build
 /.opt-in
+
+# generated/external transifex translation files per language
+/translations
diff --git a/package.json b/package.json
index ef161dfa7615d898b5b16237c71b6437ff51eba5..607b69edbdc3d02db071ad6be24ed0a9558cee07 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
     "build": "npm run clean && webpack --progress --colors --bail",
     "clean": "rimraf ./build && mkdirp build",
     "deploy": "touch build/.nojekyll && gh-pages -t -d build -m \"Build for $(git log --pretty=format:%H -n1)\"",
+    "i18n:src": "babel src > tmp.js && rimraf tmp.js && ./scripts/build-i18n-source.js ./translations/messages/ ./translations/",
     "lint": "eslint . --ext .js,.jsx",
     "start": "webpack-dev-server",
     "test": "npm run lint && npm run build",
@@ -25,9 +26,11 @@
   },
   "devDependencies": {
     "autoprefixer": "7.1.2",
+    "babel-cli": "6.24.1",
     "babel-core": "^6.23.1",
     "babel-eslint": "^7.1.1",
     "babel-loader": "^7.0.0",
+    "babel-plugin-react-intl": "2.3.1",
     "babel-plugin-transform-object-rest-spread": "^6.22.0",
     "babel-preset-es2015": "^6.22.0",
     "babel-preset-react": "^6.22.0",
@@ -55,8 +58,8 @@
     "react": "15.5.4",
     "react-dom": "15.5.4",
     "react-draggable": "2.2.6",
-    "react-modal": "2.2.1",
     "react-intl": "2.3.0",
+    "react-modal": "2.2.1",
     "react-redux": "5.0.5",
     "react-style-proptype": "3.0.0",
     "react-tabs": "1.1.0",
diff --git a/scripts/build-i18n-source.js b/scripts/build-i18n-source.js
new file mode 100755
index 0000000000000000000000000000000000000000..9f6571c1c3fa39fa2918744eb84221f8e546d057
--- /dev/null
+++ b/scripts/build-i18n-source.js
@@ -0,0 +1,45 @@
+#!/usr/bin/env node
+
+const fs = require('fs');
+const glob = require('glob');
+const path = require('path');
+const mkdirp = require('mkdirp');
+
+var args = process.argv.slice(2);
+
+if (!args.length) {
+    process.stdout.write('You must specify the messages dir generated by babel-plugin-react-intl.\n');
+    process.exit(1);
+}
+
+const MESSAGES_PATTERN = args.shift() + '/**/*.json';
+
+if (!args.length) {
+    process.stdout.write('A destination directory must be specified.\n');
+    process.exit(1);
+}
+
+const LANG_DIR = args.shift();
+
+// Aggregates the default messages that were extracted from the example app's
+// React components via the React Intl Babel plugin. An error will be thrown if
+// there are messages in different components that use the same `id`. The result
+// is a chromei18n format collection of `id: {message: defaultMessage,
+// description: description}` pairs for the app's default locale.
+let defaultMessages = glob.sync(MESSAGES_PATTERN)
+    .map((filename) => fs.readFileSync(filename, 'utf8'))
+    .map((file) => JSON.parse(file))
+    .reduce((collection, descriptors) => {
+        descriptors.forEach(({id, defaultMessage, description}) => {
+            if (collection.hasOwnProperty(id)) {
+                throw new Error(`Duplicate message id: ${id}`);
+            }
+
+            collection[id] = {message: defaultMessage, description: description};
+        });
+
+        return collection;
+    }, {});
+
+mkdirp.sync(LANG_DIR);
+fs.writeFileSync(path.join(LANG_DIR, 'en.json'), JSON.stringify(defaultMessages, null, 2));
diff --git a/webpack.config.js b/webpack.config.js
index 3ae6a87aa73152d7edf600ccece6ac1fc95fb55a..afb0924977cbaf391636391812b268d32f44beb0 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -35,7 +35,11 @@ module.exports = {
             loader: 'babel-loader',
             include: path.resolve(__dirname, 'src'),
             options: {
-                plugins: ['transform-object-rest-spread'],
+                plugins: [
+                    'transform-object-rest-spread',
+                    ['react-intl', {
+                        messagesDir: './translations/messages/'
+                    }]],
                 presets: ['es2015', 'react']
             }
         },