diff --git a/.jshintignore b/.jshintignore
index cd0d011354dc38e0a64c746b351e74ae98cd24c6..5b3cf6d8c9c7291f5fe09db0b175dcf717dc7c18 100644
--- a/.jshintignore
+++ b/.jshintignore
@@ -1,2 +1,4 @@
 **/vendor
 node_modules
+cms/static/js/i18n/**/*.js
+lms/static/js/i18n/**/*.js
diff --git a/cms/envs/common.py b/cms/envs/common.py
index 359d3c3dc8fa3c5f5ec0ab33033fccdf9ac2d970..7760d27e4338f58fd8b567467845d5c0424bff04 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -60,6 +60,8 @@ from lms.envs.common import (
 
     # Django REST framework configuration
     REST_FRAMEWORK,
+
+    STATICI18N_OUTPUT_DIR
 )
 from path import Path as path
 from warnings import simplefilter
@@ -465,6 +467,8 @@ LANGUAGE_DICT = dict(LANGUAGES)
 USE_I18N = True
 USE_L10N = True
 
+STATICI18N_ROOT = PROJECT_ROOT / "static"
+
 # Localization strings (e.g. django.po) are under this directory
 LOCALE_PATHS = (REPO_ROOT + '/conf/locale',)  # edx-platform/conf/locale/
 
@@ -845,6 +849,9 @@ INSTALLED_APPS = (
 
     # edx-milestones service
     'milestones',
+
+    # Static i18n support
+    'statici18n',
 )
 
 
diff --git a/cms/static/cms/js/require-config.js b/cms/static/cms/js/require-config.js
index 4ceb2635bd7954d29ee2a5128d7299d8ef86ba5d..167ed7617488b0dbb6716c7b54496087f2d5be77 100644
--- a/cms/static/cms/js/require-config.js
+++ b/cms/static/cms/js/require-config.js
@@ -1,289 +1,303 @@
-if (window) {
-    // MathJax Fast Preview was introduced in 2.5. However, it
-    // causes undesirable flashing/font size changes when
-    // MathJax is used for interactive preview (equation editor).
-    // Setting processSectionDelay to 0 (see below) fully eliminates
-    // fast preview, but to reduce confusion, we are also setting
-    // the option as displayed in the context menu to false.
-    // When upgrading to 2.6, check if this variable name changed.
-    window.MathJax = {
-      menuSettings: {CHTMLpreview: false}
-    };
-}
+;(function (require, define) {
+    'use strict';
+    if (window) {
+        // MathJax Fast Preview was introduced in 2.5. However, it
+        // causes undesirable flashing/font size changes when
+        // MathJax is used for interactive preview (equation editor).
+        // Setting processSectionDelay to 0 (see below) fully eliminates
+        // fast preview, but to reduce confusion, we are also setting
+        // the option as displayed in the context menu to false.
+        // When upgrading to 2.6, check if this variable name changed.
+        window.MathJax = {
+            menuSettings: {CHTMLpreview: false}
+        };
+        // Since we are serving the gettext catalog as static files,
+        // the URL for the gettext file will vary depending on which locale
+        // needs to be served. To handle this, we load the correct file in the
+        // rendered template and then use this to ensure that RequireJS knows 
+        // how to find it.
+        define("gettext", function () { return window.gettext; });
+    }
 
-require.config({
-    // NOTE: baseUrl has been previously set in cms/static/templates/base.html
-    waitSeconds: 60,
-    paths: {
-        "domReady": "js/vendor/domReady",
-        "gettext": "/i18n",
-        "mustache": "js/vendor/mustache",
-        "codemirror": "js/vendor/codemirror-compressed",
-        "codemirror/stex": "js/vendor/CodeMirror/stex",
-        "jquery": "js/vendor/jquery.min",
-        "jquery.ui": "js/vendor/jquery-ui.min",
-        "jquery.form": "js/vendor/jquery.form",
-        "jquery.markitup": "js/vendor/markitup/jquery.markitup",
-        "jquery.leanModal": "js/vendor/jquery.leanModal",
-        "jquery.ajaxQueue": "js/vendor/jquery.ajaxQueue",
-        "jquery.smoothScroll": "js/vendor/jquery.smooth-scroll.min",
-        "jquery.timepicker": "js/vendor/timepicker/jquery.timepicker",
-        "jquery.cookie": "js/vendor/jquery.cookie",
-        "jquery.qtip": "js/vendor/jquery.qtip.min",
-        "jquery.scrollTo": "js/vendor/jquery.scrollTo-1.4.2-min",
-        "jquery.flot": "js/vendor/flot/jquery.flot.min",
-        "jquery.fileupload": "js/vendor/jQuery-File-Upload/js/jquery.fileupload",
-        "jquery.fileupload-process": "js/vendor/jQuery-File-Upload/js/jquery.fileupload-process",
-        "jquery.fileupload-validate": "js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate",
-        "jquery.iframe-transport": "js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
-        "jquery.inputnumber": "js/vendor/html5-input-polyfills/number-polyfill",
-        "jquery.immediateDescendents": "coffee/src/jquery.immediateDescendents",
-        "datepair": "js/vendor/timepicker/datepair",
-        "date": "js/vendor/date",
-        "moment": "js/vendor/moment.min",
-        "moment-with-locales": "js/vendor/moment-with-locales.min",
-        "text": 'js/vendor/requirejs/text',
-        "underscore": "js/vendor/underscore-min",
-        "underscore.string": "js/vendor/underscore.string.min",
-        "backbone": "js/vendor/backbone-min",
-        "backbone-relational" : "js/vendor/backbone-relational.min",
-        "backbone.associations": "js/vendor/backbone-associations-min",
-        "backbone.paginator": "js/vendor/backbone.paginator.min",
-        "tinymce": "js/vendor/tinymce/js/tinymce/tinymce.full.min",
-        "jquery.tinymce": "js/vendor/tinymce/js/tinymce/jquery.tinymce.min",
-        "xmodule": "/xmodule/xmodule",
-        "xblock/core": "js/xblock/core",
-        "xblock": "coffee/src/xblock",
-        "utility": "js/src/utility",
-        "accessibility": "js/src/accessibility_tools",
-        "draggabilly": "js/vendor/draggabilly.pkgd",
-        "URI": "js/vendor/URI.min",
-        "ieshim": "js/src/ie_shim",
-        "tooltip_manager": "js/src/tooltip_manager",
+    require.config({
+        // NOTE: baseUrl has been previously set in cms/static/templates/base.html
+        waitSeconds: 60,
+        paths: {
+            "domReady": "js/vendor/domReady",
+            "mustache": "js/vendor/mustache",
+            "codemirror": "js/vendor/codemirror-compressed",
+            "codemirror/stex": "js/vendor/CodeMirror/stex",
+            "jquery": "js/vendor/jquery.min",
+            "jquery.ui": "js/vendor/jquery-ui.min",
+            "jquery.form": "js/vendor/jquery.form",
+            "jquery.markitup": "js/vendor/markitup/jquery.markitup",
+            "jquery.leanModal": "js/vendor/jquery.leanModal",
+            "jquery.ajaxQueue": "js/vendor/jquery.ajaxQueue",
+            "jquery.smoothScroll": "js/vendor/jquery.smooth-scroll.min",
+            "jquery.timepicker": "js/vendor/timepicker/jquery.timepicker",
+            "jquery.cookie": "js/vendor/jquery.cookie",
+            "jquery.qtip": "js/vendor/jquery.qtip.min",
+            "jquery.scrollTo": "js/vendor/jquery.scrollTo-1.4.2-min",
+            "jquery.flot": "js/vendor/flot/jquery.flot.min",
+            "jquery.fileupload": "js/vendor/jQuery-File-Upload/js/jquery.fileupload",
+            "jquery.fileupload-process": "js/vendor/jQuery-File-Upload/js/jquery.fileupload-process",
+            "jquery.fileupload-validate": "js/vendor/jQuery-File-Upload/js/jquery.fileupload-validate",
+            "jquery.iframe-transport": "js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
+            "jquery.inputnumber": "js/vendor/html5-input-polyfills/number-polyfill",
+            "jquery.immediateDescendents": "coffee/src/jquery.immediateDescendents",
+            "datepair": "js/vendor/timepicker/datepair",
+            "date": "js/vendor/date",
+            "moment": "js/vendor/moment.min",
+            "moment-with-locales": "js/vendor/moment-with-locales.min",
+            "text": 'js/vendor/requirejs/text',
+            "underscore": "js/vendor/underscore-min",
+            "underscore.string": "js/vendor/underscore.string.min",
+            "backbone": "js/vendor/backbone-min",
+            "backbone-relational" : "js/vendor/backbone-relational.min",
+            "backbone.associations": "js/vendor/backbone-associations-min",
+            "backbone.paginator": "js/vendor/backbone.paginator.min",
+            "tinymce": "js/vendor/tinymce/js/tinymce/tinymce.full.min",
+            "jquery.tinymce": "js/vendor/tinymce/js/tinymce/jquery.tinymce.min",
+            "xmodule": "/xmodule/xmodule",
+            "xblock/core": "js/xblock/core",
+            "xblock": "coffee/src/xblock",
+            "utility": "js/src/utility",
+            "accessibility": "js/src/accessibility_tools",
+            "draggabilly": "js/vendor/draggabilly.pkgd",
+            "URI": "js/vendor/URI.min",
+            "ieshim": "js/src/ie_shim",
+            "tooltip_manager": "js/src/tooltip_manager",
 
-        // Files needed for Annotations feature
-        "annotator": "js/vendor/ova/annotator-full",
-        "annotator-harvardx": "js/vendor/ova/annotator-full-firebase-auth",
-        "video.dev": "js/vendor/ova/video.dev",
-        "vjs.youtube": 'js/vendor/ova/vjs.youtube',
-        "rangeslider": 'js/vendor/ova/rangeslider',
-        "share-annotator": 'js/vendor/ova/share-annotator',
-        "richText-annotator": 'js/vendor/ova/richText-annotator',
-        "reply-annotator": 'js/vendor/ova/reply-annotator',
-        "grouping-annotator": 'js/vendor/ova/grouping-annotator',
-        "tags-annotator": 'js/vendor/ova/tags-annotator',
-        "diacritic-annotator": 'js/vendor/ova/diacritic-annotator',
-        "flagging-annotator": 'js/vendor/ova/flagging-annotator',
-        "jquery-Watch": 'js/vendor/ova/jquery-Watch',
-        "openseadragon": 'js/vendor/ova/openseadragon',
-        "osda": 'js/vendor/ova/OpenSeaDragonAnnotation',
-        "ova": 'js/vendor/ova/ova',
-        "catch": 'js/vendor/ova/catch/js/catch',
-        "handlebars": 'js/vendor/ova/catch/js/handlebars-1.1.2',
-        // end of Annotation tool files
+            // Files needed for Annotations feature
+            "annotator": "js/vendor/ova/annotator-full",
+            "annotator-harvardx": "js/vendor/ova/annotator-full-firebase-auth",
+            "video.dev": "js/vendor/ova/video.dev",
+            "vjs.youtube": 'js/vendor/ova/vjs.youtube',
+            "rangeslider": 'js/vendor/ova/rangeslider',
+            "share-annotator": 'js/vendor/ova/share-annotator',
+            "richText-annotator": 'js/vendor/ova/richText-annotator',
+            "reply-annotator": 'js/vendor/ova/reply-annotator',
+            "grouping-annotator": 'js/vendor/ova/grouping-annotator',
+            "tags-annotator": 'js/vendor/ova/tags-annotator',
+            "diacritic-annotator": 'js/vendor/ova/diacritic-annotator',
+            "flagging-annotator": 'js/vendor/ova/flagging-annotator',
+            "jquery-Watch": 'js/vendor/ova/jquery-Watch',
+            "openseadragon": 'js/vendor/ova/openseadragon',
+            "osda": 'js/vendor/ova/OpenSeaDragonAnnotation',
+            "ova": 'js/vendor/ova/ova',
+            "catch": 'js/vendor/ova/catch/js/catch',
+            "handlebars": 'js/vendor/ova/catch/js/handlebars-1.1.2',
+            // end of Annotation tool files
 
-        // externally hosted files
-        "mathjax": "//cdn.mathjax.org/mathjax/2.5-latest/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured",
-        "youtube": [
-            // youtube URL does not end in ".js". We add "?noext" to the path so
-            // that require.js adds the ".js" to the query component of the URL,
-            // and leaves the path component intact.
-            "//www.youtube.com/player_api?noext",
-            // if youtube fails to load, fallback on a local file
-            // so that require doesn't fall over
-            "js/src/youtube_fallback"
-        ]
-    },
-    shim: {
-        "gettext": {
-            exports: "gettext"
-        },
-        "date": {
-            exports: "Date"
-        },
-        "jquery.ui": {
-            deps: ["jquery"],
-            exports: "jQuery.ui"
-        },
-        "jquery.form": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.ajaxForm"
-        },
-        "jquery.markitup": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.markitup"
-        },
-        "jquery.leanmodal": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.leanModal"
-        },
-        "jquery.ajaxQueue": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.ajaxQueue"
-        },
-        "jquery.smoothScroll": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.smoothScroll"
-        },
-        "jquery.cookie": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.cookie"
-        },
-        "jquery.qtip": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.qtip"
-        },
-        "jquery.scrollTo": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.scrollTo",
-        },
-        "jquery.flot": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.plot"
-        },
-        "jquery.fileupload": {
-            deps: ["jquery.ui", "jquery.iframe-transport"],
-            exports: "jQuery.fn.fileupload"
-        },
-        "jquery.fileupload-process": {
-            deps: ["jquery.fileupload"]
-        },
-        "jquery.fileupload-validate": {
-            deps: ["jquery.fileupload"]
-        },
-        "jquery.inputnumber": {
-            deps: ["jquery"],
-            exports: "jQuery.fn.inputNumber"
-        },
-        "jquery.tinymce": {
-            deps: ["jquery", "tinymce"],
-            exports: "jQuery.fn.tinymce"
-        },
-        "datepair": {
-            deps: ["jquery.ui", "jquery.timepicker"]
-        },
-        "underscore": {
-            exports: "_"
-        },
-        "backbone": {
-            deps: ["underscore", "jquery"],
-            exports: "Backbone"
-        },
-        "backbone.associations": {
-            deps: ["backbone"],
-            exports: "Backbone.Associations"
-        },
-        "backbone.paginator": {
-            deps: ["backbone"],
-            exports: "Backbone.Paginator"
-        },
-        "youtube": {
-            exports: "YT"
-        },
-        "codemirror": {
-            exports: "CodeMirror"
-        },
-        "codemirror/stex": {
-            deps: ["codemirror"]
-        },
-        "tinymce": {
-            exports: "tinymce"
-        },
-        "mathjax": {
-            exports: "MathJax",
-            init: function() {
-              MathJax.Hub.Config({
-                tex2jax: {
-                  inlineMath: [
-                    ["\\(","\\)"],
-                    ['[mathjaxinline]','[/mathjaxinline]']
-                  ],
-                  displayMath: [
-                    ["\\[","\\]"],
-                    ['[mathjax]','[/mathjax]']
-                  ]
+            // externally hosted files
+            "mathjax": "//cdn.mathjax.org/mathjax/2.5-latest/MathJax.js?config=TeX-MML-AM_HTMLorMML-full&delayStartupUntil=configured", // jshint ignore:line
+            "youtube": [
+                // youtube URL does not end in ".js". We add "?noext" to the path so
+                // that require.js adds the ".js" to the query component of the URL,
+                // and leaves the path component intact.
+                "//www.youtube.com/player_api?noext",
+                // if youtube fails to load, fallback on a local file
+                // so that require doesn't fall over
+                "js/src/youtube_fallback"
+            ]
+        },
+        shim: {
+            "gettext": {
+                exports: "gettext"
+            },
+            "date": {
+                exports: "Date"
+            },
+            "jquery.ui": {
+                deps: ["jquery"],
+                exports: "jQuery.ui"
+            },
+            "jquery.form": {
+                deps: ["jquery"],
+                exports: "jQuery.fn.ajaxForm"
+            },
+            "jquery.markitup": {
+                deps: ["jquery"],
+                exports: "jQuery.fn.markitup"
+            },
+            "jquery.leanmodal": {
+                deps: ["jquery"],
+                exports: "jQuery.fn.leanModal"
+            },
+            "jquery.ajaxQueue": {
+                deps: ["jquery"],
+                exports: "jQuery.fn.ajaxQueue"
+            },
+            "jquery.smoothScroll": {
+                deps: ["jquery"],
+                exports: "jQuery.fn.smoothScroll"
+            },
+            "jquery.cookie": {
+                deps: ["jquery"],
+                exports: "jQuery.fn.cookie"
+            },
+            "jquery.qtip": {
+                deps: ["jquery"],
+                exports: "jQuery.fn.qtip"
+            },
+            "jquery.scrollTo": {
+                deps: ["jquery"],
+                exports: "jQuery.fn.scrollTo",
+            },
+            "jquery.flot": {
+                deps: ["jquery"],
+                exports: "jQuery.fn.plot"
+            },
+            "jquery.fileupload": {
+                deps: ["jquery.ui", "jquery.iframe-transport"],
+                exports: "jQuery.fn.fileupload"
+            },
+            "jquery.fileupload-process": {
+                deps: ["jquery.fileupload"]
+            },
+            "jquery.fileupload-validate": {
+                deps: ["jquery.fileupload"]
+            },
+            "jquery.inputnumber": {
+                deps: ["jquery"],
+                exports: "jQuery.fn.inputNumber"
+            },
+            "jquery.tinymce": {
+                deps: ["jquery", "tinymce"],
+                exports: "jQuery.fn.tinymce"
+            },
+            "datepair": {
+                deps: ["jquery.ui", "jquery.timepicker"]
+            },
+            "underscore": {
+                exports: "_"
+            },
+            "backbone": {
+                deps: ["underscore", "jquery"],
+                exports: "Backbone"
+            },
+            "backbone.associations": {
+                deps: ["backbone"],
+                exports: "Backbone.Associations"
+            },
+            "backbone.paginator": {
+                deps: ["backbone"],
+                exports: "Backbone.Paginator"
+            },
+            "youtube": {
+                exports: "YT"
+            },
+            "codemirror": {
+                exports: "CodeMirror"
+            },
+            "codemirror/stex": {
+                deps: ["codemirror"]
+            },
+            "tinymce": {
+                exports: "tinymce"
+            },
+            "mathjax": {
+                exports: "MathJax",
+                init: function() {
+                    window.MathJax.Hub.Config({
+                        tex2jax: {
+                            inlineMath: [
+                                ["\\(","\\)"],
+                                ['[mathjaxinline]','[/mathjaxinline]']
+                            ],
+                            displayMath: [
+                                ["\\[","\\]"],
+                                ['[mathjax]','[/mathjax]']
+                            ]
+                        }
+                });
+                // In order to eliminate all flashing during interactive
+                // preview, it is necessary to set processSectionDelay to 0
+                // (remove delay between input and output phases). This
+                // effectively disables fast preview, regardless of
+                // the fast preview setting as shown in the context menu.
+                window.MathJax.Hub.processSectionDelay = 0;
+                window.MathJax.Hub.Configured();
                 }
-              });
-              // In order to eliminate all flashing during interactive
-              // preview, it is necessary to set processSectionDelay to 0
-              // (remove delay between input and output phases). This
-              // effectively disables fast preview, regardless of
-              // the fast preview setting as shown in the context menu.
-              MathJax.Hub.processSectionDelay = 0;
-              MathJax.Hub.Configured();
-            }
-        },
-        "URI": {
-            exports: "URI"
-        },
-        "tooltip_manager": {
-            deps: ["jquery", "underscore"]
-        },
-        "jquery.immediateDescendents": {
-            deps: ["jquery"]
-        },
-        "xblock/core": {
-            exports: "XBlock",
-            deps: ["jquery", "jquery.immediateDescendents"]
-        },
-        "xblock/runtime.v1": {
-            exports: "XBlock",
-            deps: ["xblock/core"]
-        },
+            },
+            "URI": {
+                exports: "URI"
+            },
+            "tooltip_manager": {
+                deps: ["jquery", "underscore"]
+            },
+            "jquery.immediateDescendents": {
+                deps: ["jquery"]
+            },
+            "xblock/core": {
+                exports: "XBlock",
+                deps: ["jquery", "jquery.immediateDescendents"]
+            },
+            "xblock/runtime.v1": {
+                exports: "XBlock",
+                deps: ["xblock/core"]
+            },
 
-        "coffee/src/main": {
-            deps: ["coffee/src/ajax_prefix"]
-        },
-        "js/src/logger": {
-            exports: "Logger",
-            deps: ["coffee/src/ajax_prefix"]
-        },
+            "coffee/src/main": {
+                deps: ["coffee/src/ajax_prefix"]
+            },
+            "js/src/logger": {
+                exports: "Logger",
+                deps: ["coffee/src/ajax_prefix"]
+            },
 
-        // the following are all needed for annotation tools
-        "video.dev": {
-            exports:"videojs"
-        },
-        "vjs.youtube": {
-            deps: ["video.dev"]
-        },
-        "rangeslider": {
-            deps: ["video.dev"]
-        },
-        "annotator": {
-            exports: "Annotator"
-        },
-        "annotator-harvardx":{
-            deps: ["annotator"]
-        },
-        "share-annotator": {
-            deps: ["annotator"]
-        },
-        "richText-annotator": {
-            deps: ["annotator", "tinymce"]
-        },
-        "reply-annotator": {
-            deps: ["annotator"]
-        },
-        "tags-annotator": {
-            deps: ["annotator"]
-        },
-        "diacritic-annotator": {
-            deps: ["annotator"]
-        },
-        "flagging-annotator": {
-            deps: ["annotator"]
-        },
-        "grouping-annotator": {
-            deps: ["annotator"]
-        },
-        "ova":{
-            exports: "ova",
-            deps: ["annotator", "annotator-harvardx", "video.dev", "vjs.youtube", "rangeslider", "share-annotator", "richText-annotator", "reply-annotator", "tags-annotator", "flagging-annotator", "grouping-annotator", "diacritic-annotator", "jquery-Watch", "catch", "handlebars", "URI"]
-        },
-        "osda":{
-            exports: "osda",
-            deps: ["annotator", "annotator-harvardx", "video.dev", "vjs.youtube", "rangeslider", "share-annotator", "richText-annotator", "reply-annotator", "tags-annotator", "flagging-annotator", "grouping-annotator", "diacritic-annotator", "openseadragon", "jquery-Watch", "catch", "handlebars", "URI"]
+            // the following are all needed for annotation tools
+            "video.dev": {
+                exports:"videojs"
+            },
+            "vjs.youtube": {
+                deps: ["video.dev"]
+            },
+            "rangeslider": {
+                deps: ["video.dev"]
+            },
+            "annotator": {
+                exports: "Annotator"
+            },
+            "annotator-harvardx":{
+                deps: ["annotator"]
+            },
+            "share-annotator": {
+                deps: ["annotator"]
+            },
+            "richText-annotator": {
+                deps: ["annotator", "tinymce"]
+            },
+            "reply-annotator": {
+                deps: ["annotator"]
+            },
+            "tags-annotator": {
+                deps: ["annotator"]
+            },
+            "diacritic-annotator": {
+                deps: ["annotator"]
+            },
+            "flagging-annotator": {
+                deps: ["annotator"]
+            },
+            "grouping-annotator": {
+                deps: ["annotator"]
+            },
+            "ova":{
+                exports: "ova",
+                deps: ["annotator", "annotator-harvardx", "video.dev", "vjs.youtube", 
+                       "rangeslider", "share-annotator", "richText-annotator", "reply-annotator", 
+                       "tags-annotator", "flagging-annotator", "grouping-annotator", "diacritic-annotator", 
+                       "jquery-Watch", "catch", "handlebars", "URI"]
+            },
+            "osda":{
+                exports: "osda",
+                deps: ["annotator", "annotator-harvardx", "video.dev", "vjs.youtube", 
+                       "rangeslider", "share-annotator", "richText-annotator", "reply-annotator", 
+                       "tags-annotator", "flagging-annotator", "grouping-annotator", "diacritic-annotator", 
+                       "openseadragon", "jquery-Watch", "catch", "handlebars", "URI"]
+            }
+            // end of annotation tool files
         }
-        // end of annotation tool files
-    }
-});
+    });
+}).call(this, require, define);
diff --git a/cms/templates/base.html b/cms/templates/base.html
index 9bacefda13beae4a1b0b81132123ecbb901d5706..32ce03dcdd78639c8b721b488219ea7da2a58f18 100644
--- a/cms/templates/base.html
+++ b/cms/templates/base.html
@@ -23,6 +23,11 @@ from openedx.core.lib.js_utils import (
         ${settings.STUDIO_NAME}
     </title>
 
+    <%
+      jsi18n_path = "js/i18n/{language}/djangojs.js".format(language=LANGUAGE_CODE)
+    %>
+
+    <script type="text/javascript" src="${static.url(jsi18n_path)}"></script>
     <meta name="viewport" content="width=device-width,initial-scale=1">
     <meta name="path_prefix" content="${EDX_ROOT_URL}">
 
diff --git a/cms/urls.py b/cms/urls.py
index 5c3e8ed1cf09e45f335fc59984818e77720439a3..2b85e924719e5cbd885da5f71cd976271c877c42 100644
--- a/cms/urls.py
+++ b/cms/urls.py
@@ -122,12 +122,6 @@ JS_INFO_DICT = {
     'packages': ('openassessment',),
 }
 
-urlpatterns += patterns(
-    '',
-    # Serve catalog of localized strings to be rendered by Javascript
-    url(r'^i18n.js$', 'django.views.i18n.javascript_catalog', JS_INFO_DICT),
-)
-
 if settings.FEATURES.get('ENABLE_CONTENT_LIBRARIES'):
     urlpatterns += (
         url(r'^library/{}?$'.format(LIBRARY_KEY_PATTERN),
diff --git a/conf/locale/config.yaml b/conf/locale/config.yaml
index a029efc5da92b4c3f9dd51b23a8b43ddb699a737..d2410f681088091651bd1a7b8d39949b5623ee1e 100644
--- a/conf/locale/config.yaml
+++ b/conf/locale/config.yaml
@@ -111,6 +111,9 @@ ignore_dirs:
     - '*/spec'
     - '*/tests'
     - '*/features'
+    # Directories full of auto-generated JS
+    - lms/static/js/i18n
+    - cms/static/js/i18n
 
 
 # Third-party installed apps that we also extract strings from.  When adding a
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 6ab46ebe0d244c117190ca105154053087c564d4..a1aae3015579358eeee30d372eaf9e9aa55ccdd2 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -922,6 +922,9 @@ LANGUAGE_DICT = dict(LANGUAGES)
 USE_I18N = True
 USE_L10N = True
 
+STATICI18N_ROOT = PROJECT_ROOT / "static"
+STATICI18N_OUTPUT_DIR = "js/i18n"
+
 # Localization strings (e.g. django.po) are under this directory
 LOCALE_PATHS = (REPO_ROOT + '/conf/locale',)  # edx-platform/conf/locale/
 # Messages
@@ -1943,6 +1946,9 @@ INSTALLED_APPS = (
 
     # Gating of course content
     'gating.apps.GatingConfig',
+
+    # Static i18n support
+    'statici18n',
 )
 
 # Migrations which are not in the standard module "migrations"
diff --git a/lms/static/lms/js/require-config.js b/lms/static/lms/js/require-config.js
index acc0a3b3bcce4694f5ef6206c16761d83f6ab578..2674176e7da11a0ff7beeb2131e6ed2d98297e75 100644
--- a/lms/static/lms/js/require-config.js
+++ b/lms/static/lms/js/require-config.js
@@ -32,7 +32,6 @@
         // NOTE: baseUrl has been previously set in lms/templates/main.html
         waitSeconds: 60,
         paths: {
-            "gettext": "/i18n",
             "annotator_1.2.9": "js/vendor/edxnotes/annotator-full.min",
             "date": "js/vendor/date",
             "moment": "js/vendor/moment.min",
diff --git a/lms/templates/main.html b/lms/templates/main.html
index 03c3b3f46c0ec679e4c58057ade61e794f604a85..0801da7e195a2edebf645d18db89605a5a7e5536 100644
--- a/lms/templates/main.html
+++ b/lms/templates/main.html
@@ -51,7 +51,11 @@ from branding import api as branding_api
       </script>
   % endif
 
-  <script type="text/javascript" src="/i18n.js"></script>
+  <%
+    jsi18n_path = "js/i18n/{language}/djangojs.js".format(language=LANGUAGE_CODE)
+  %>
+
+  <script type="text/javascript" src="${static.url(jsi18n_path)}"></script>
 
   <link rel="icon" type="image/x-icon" href="${static.url(static.get_value('favicon_path', settings.FAVICON_PATH))}" />
 
diff --git a/lms/urls.py b/lms/urls.py
index 3ea312edc252f889a69057c5ff6fb044cdbcb727..f21f8821c0b2fad700ac22effb257f5d3d4cabdf 100644
--- a/lms/urls.py
+++ b/lms/urls.py
@@ -138,11 +138,6 @@ js_info_dict = {
     'packages': ('openassessment',),
 }
 
-urlpatterns += (
-    # Serve catalog of localized strings to be rendered by Javascript
-    url(r'^i18n.js$', 'django.views.i18n.javascript_catalog', js_info_dict),
-)
-
 # sysadmin dashboard, to see what courses are loaded, to delete & load courses
 if settings.FEATURES["ENABLE_SYSADMIN_DASHBOARD"]:
     urlpatterns += (
diff --git a/pavelib/i18n.py b/pavelib/i18n.py
index cd6ed7561c8cac35e3ca3ff2559707557bb5a44b..cc7a622b2d7d56f28992387e507ae97f88ac5893 100644
--- a/pavelib/i18n.py
+++ b/pavelib/i18n.py
@@ -5,12 +5,15 @@ import sys
 import subprocess
 from path import Path as path
 from paver.easy import task, cmdopts, needs, sh
+from .utils.cmd import django_cmd
 
 try:
     from pygments.console import colorize
 except ImportError:
     colorize = lambda color, text: text
 
+DEFAULT_SETTINGS = 'devstack'
+
 
 @task
 @needs(
@@ -194,6 +197,7 @@ def i18n_robot_pull():
     """
     Pull source strings, generate po and mo files, and validate
     """
+
     # sh('paver test_i18n')
     # Tests were removed from repo, but there should still be tests covering the translations
     # TODO: Validate the recently pulled translations, and give a bail option
@@ -203,10 +207,16 @@ def i18n_robot_pull():
     print "\n\nValidating translations with `i18n_tool validate`..."
     sh("{cmd}".format(cmd=cmd))
 
+    # Generate static i18n JS files.
+    for system in ['lms', 'cms']:
+        sh(django_cmd(system, DEFAULT_SETTINGS, 'compilejsi18n'))
+
     con = raw_input("Continue with committing these translations (y/n)? ")
 
     if con.lower() == 'y':
         sh('git add conf/locale')
+        sh('git add cms/static/js/i18n')
+        sh('git add lms/static/js/i18n')
 
         sh(
             'git commit --message='
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index 4ba13655e670c9938df3eb1f393496b20c825ba0..6c9d452cece2c593453c4e558435c9789c6c1989 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -26,6 +26,7 @@ django-oauth-plus==2.2.8
 django-sekizai==0.8.2
 django-ses==0.7.0
 django-simple-history==1.6.3
+django-statici18n==1.1.5
 django-storages-redux==1.3
 django-method-override==0.1.0
 # We need a fix to DRF 3.2.x, for now use it from our own cherry-picked repo