diff --git a/bin/browserid b/bin/browserid
index 225526ab27a818edbc3f7545e0f216b088c5573f..f3399bb57408478b01731f1f6993375150f544b3 100755
--- a/bin/browserid
+++ b/bin/browserid
@@ -61,7 +61,7 @@ app.use(i18n.abide({
   supported_languages: config.get('supported_languages'),
   default_lang: config.get('default_lang'),
   debug_lang: config.get('debug_lang'),
-  locale_directory: config.get('locale_directory'),
+  translation_directory: config.get('translation_directory'),
   disable_locale_check: config.get('disable_locale_check')
 }));
 
diff --git a/bin/static b/bin/static
index 47af95441d6858387a0bfa09918fef5c124bdc52..d9f3894084f03958960848d9ffcc60ec085f09ef 100755
--- a/bin/static
+++ b/bin/static
@@ -53,7 +53,7 @@ app.use(i18n.abide({
   supported_languages: config.get('supported_languages'),
   default_lang: config.get('default_lang'),
   debug_lang: config.get('debug_lang'),
-  locale_directory: config.get('locale_directory'),
+  translation_directory: config.get('translation_directory'),
   disable_locale_check: config.get('disable_locale_check')
 }));
 
diff --git a/config/aws.json b/config/aws.json
index c944c2e28d64c4d0f930b044c504fc6e711b6806..336f5ed2ab338b43791738d08bb8ac660e7944cf 100644
--- a/config/aws.json
+++ b/config/aws.json
@@ -10,9 +10,9 @@
   // are inverted and reversed), and en-US.
   // This set can be overridden by adding more to config.json on the VM.
   "supported_languages": [
-    "en-US"
+    "en-US", "it-CH"
   ],
-  "locale_directory": "/home/app/code/locale",
+  "debug_lang": "it-CH",
   "var_path": "/home/app/var",
 
   "http_proxy": {
diff --git a/lib/configuration.js b/lib/configuration.js
index ef4d97f42e2609563a792783337bbc04f2697663..79d7aa72f8d398dd806e1154377eac953ba2a427 100644
--- a/lib/configuration.js
+++ b/lib/configuration.js
@@ -185,14 +185,18 @@ var conf = module.exports = convict({
   debug_lang: 'string = "it-CH"',
   supported_languages: {
     doc: "List of languages this deployment should detect and display localized strings.",
-    format: 'array { string }* = [ "en-US" ]',
+    format: 'array { string }* = [ "en-US", "it-CH" ]',
     env: 'SUPPORTED_LANGUAGES'
   },
   disable_locale_check: {
     doc: "Skip checking for gettext .mo files for supported locales",
     format: 'boolean = false'
   },
-  locale_directory: 'string = "locale"',
+  translation_directory: {
+    doc: "The directory where per-locale .json files containing translations reside",
+    format: 'string = "resources/static/i18n/"',
+    env: "TRANSLATION_DIR"
+  },
   express_log_format: 'string [ "default_bid", "dev_bid", "default", "dev", "short", "tiny" ] = "default"',
   keysigner_url: {
     format: 'string?',
@@ -256,6 +260,11 @@ if (process.env['CONFIG_FILES']) {
   });
 }
 
+// allow supported langauges to be specified in the env as a CSV string
+if (process.env['SUPPORTED_LANGUAGES']) {
+  conf.set('supported_languages', process.env['SUPPORTED_LANGUAGES'].split(','));
+}
+
 // special handling of HTTP_PROXY env var
 if (process.env['HTTP_PROXY']) {
   var p = process.env['HTTP_PROXY'].split(':');
diff --git a/lib/i18n.js b/lib/i18n.js
index 145365aea0bdfae53db1bc839e17fb568e39bf97..e81eb32982b4f174b17bed1ab68e5b57e0e7269d 100644
--- a/lib/i18n.js
+++ b/lib/i18n.js
@@ -18,7 +18,8 @@
 var logger = require('./logging.js').logger,
     path = require('path'),
     util = require('util'),
-    fs = require('fs');
+    fs = require('fs'),
+    gobbledygook = require('gobbledygook');
 
 // existsSync moved from path in 0.6.x to fs in 0.8.x
 if (typeof fs.existsSync === 'function') {
@@ -27,7 +28,7 @@ if (typeof fs.existsSync === 'function') {
   var existsSync = path.existsSync;
 }
 
-const BIDI_RTL_LANGS = ['ar', 'db-LB', 'fa', 'he'];
+const BIDI_RTL_LANGS = ['ar', 'fa', 'he'];
 
 var translations = {};
 
@@ -38,33 +39,29 @@ var translations = {};
   app.use(i18n.abide({
     supported_languages: ['en-US', 'fr', 'pl'],
     default_lang: 'en-US',
-    locale_directory: 'locale'
   }));
  *
  * Other valid options: gettext_alias, ngettext_alias
  */
 exports.abide = function (options) {
-
-  if (! options.gettext_alias)        options.gettext_alias = 'gettext';
-  if (! options.ngettext_alias)       options.ngettext_alias = 'ngettext';
-  if (! options.supported_languages)  options.supported_languages = ['en-US'];
-  if (! options.default_lang)         options.default_lang = 'en-US';
-  if (! options.debug_lang)           options.debug_lang = 'it-CH';
-  if (! options.disable_locale_check) options.disable_locale_check = false;
-  if (! options.locale_directory)     options.locale_directory = 'locale';
-  if (! options.i18n_json_dir)        options.i18n_json_dir = 'resources/static/i18n/';
+  if (! options.gettext_alias)          options.gettext_alias = 'gettext';
+  if (! options.supported_languages)    options.supported_languages = ['en-US'];
+  if (! options.default_lang)           options.default_lang = 'en-US';
+  if (! options.debug_lang)             options.debug_lang = 'it-CH';
+  if (! options.disable_locale_check)   options.disable_locale_check = false;
+  if (! options.translation_directory)  options.i18n_json_dir = 'l10n/';
 
   var json_dir = path.resolve(
           path.join(__dirname, '..'),
-          path.join(options.i18n_json_dir));
+          path.join(options.translation_directory));
 
   var debug_locale = localeFrom(options.debug_lang);
 
   options.supported_languages.forEach(function (lang, i) {
-    var l = (lang === options.debug_lang ? 'db_LB' : localeFrom(lang));
+    // ignore .json files for default and debug languages
+    if (options.default_lang == lang || options.debug_lang == lang) return;
 
-    // ignore .json files for en-US
-    if (lang == 'en-US') return;
+    var l = localeFrom(lang);
 
     try {
       // populate the in-memory translation cache with client.json, which contains
@@ -101,14 +98,10 @@ exports.abide = function (options) {
         debug_lang = options.debug_lang.toLowerCase(),
         locale;
 
-    if (lang && lang.toLowerCase && lang.toLowerCase() == debug_lang) {
-        lang = 'db-LB'; // What? http://www.youtube.com/watch?v=rJLnGjhPT1Q
-    }
-
     resp.local('lang', lang);
 
     // BIDI support, which direction does text flow?
-    lang_dir = BIDI_RTL_LANGS.indexOf(lang) >= 0 ? 'rtl' : 'ltr';
+    lang_dir = ((BIDI_RTL_LANGS.indexOf(lang) >= 0) || debug_lang == lang) ? 'rtl' : 'ltr';
     resp.local('lang_dir', lang_dir);
     req.lang = lang;
 
@@ -122,9 +115,15 @@ exports.abide = function (options) {
 
     var gt;
 
-    if (translations[locale]) {
+    if (lang.toLowerCase() === debug_lang) {
+      gt = gobbledygook;
+      resp.local('lang', 'db-LB');
+    } else if (translations[locale]) {
       gt = function(sid) {
-        return (translations[locale][sid] ? translations[locale][sid][1] : sid);
+        if (translations[locale][sid] && translations[locale][sid][1].length) {
+          sid = translations[locale][sid][1];
+        }
+        return sid;
       };
     } else {
       gt = function(a) { return a; }
diff --git a/lib/static/views.js b/lib/static/views.js
index 957bfb09c92d0b3d1e6ec25d0392c9222d6f2530..0d96e7e0894f2c91965087748cd28c6569e64a20 100644
--- a/lib/static/views.js
+++ b/lib/static/views.js
@@ -197,6 +197,15 @@ exports.setup = function(app) {
     app.get(/^\/test\/(?:index.html)?$/, function (req, res) {
       res.render('test.ejs', {title: 'Mozilla Persona QUnit Test', layout: false});
     });
+
+    // l10n test template
+    var testPath = path.join(__dirname, '..', '..', 'tests', 'i18n_test_templates');
+    app.get('/i18n_test', function(req, res) {
+      renderCachableView(req, res, path.join(testPath, 'i18n_test.ejs'), { layout: false, title: 'l10n testing title' });
+    });
+    app.get('/i18n_fallback_test', function(req, res) {
+      renderCachableView(req, res, path.join(testPath, 'i18n_fallback_test.ejs'), { layout: false, title: 'l10n testing title' });
+    });
   } else {
     // this is stage or production, explicitly disable all resources under /test
     app.get(/^\/test/, function(req, res) {
diff --git a/lib/static_resources.js b/lib/static_resources.js
index 31cf4e08e158a5cbb870a13daeec2bb4138b2b2e..87575a1375eea7c4f0ab9aace005ce4f8df9f019 100644
--- a/lib/static_resources.js
+++ b/lib/static_resources.js
@@ -23,9 +23,9 @@ var common_js = [
   '/common/js/lib/ejs.js',
   '/common/js/lib/micrajax.js',
   '/common/js/lib/urlparse.js',
+  '/common/js/lib/gobbledygook.js',
   '/common/js/javascript-extensions.js',
   '/i18n/:locale/client.json',
-  '/common/js/gettext.js',
   '/common/js/browserid.js',
   '/common/js/lib/hub.js',
   '/common/js/lib/dom-jquery.js',
@@ -39,6 +39,7 @@ var common_js = [
   '/common/js/validation.js',
   '/common/js/helpers.js',
   '/common/js/dom-helpers.js',
+  '/common/js/gettext.js',
   '/common/js/screens.js',
   '/common/js/browser-support.js',
   '/common/js/enable_cookies_url.js',
diff --git a/lib/wsapi.js b/lib/wsapi.js
index cabedf038c27df25dc4dcb4f6b0eb765b8f8e8a7..7cee435e2afba35e628923bb3a1c2583447eca40 100644
--- a/lib/wsapi.js
+++ b/lib/wsapi.js
@@ -34,7 +34,7 @@ i18n = require('./i18n');
 var abide = i18n.abide({
   supported_languages: config.get('supported_languages'),
   default_lang: config.get('default_lang'),
-  locale_directory: config.get('locale_directory'),
+  translation_directory: config.get('translation_directory'),
   disable_locale_check: config.get('disable_locale_check')
 });
 
diff --git a/package.json b/package.json
index 2d1810ee6348b898996d666ebd354ee317e9d2cd..fda87e86ad8085eed862b99af52c51f1902a6442 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
         "ejs": "0.4.3",
         "etagify": "0.0.2",
         "express": "2.5.0",
+        "gobbledygook": "0.0.3", 
         "mustache": "0.3.1-dev",
         "jwcrypto": "0.3.2",
         "mysql": "0.9.5",
diff --git a/resources/static/common/js/gettext.js b/resources/static/common/js/gettext.js
index a5d3413949a8c464599e7114cad10564d8144a19..a8794883efadfdd829b5e79df8a6c2d29c0d88da 100644
--- a/resources/static/common/js/gettext.js
+++ b/resources/static/common/js/gettext.js
@@ -6,17 +6,25 @@
 (function() {
   "use strict";
 
+  var bid = BrowserID,
+      dom = bid.DOM;
+
   function Gettext() {
       return {
         gettext: function (msgid) {
+          if (window.Gobbledygook &&
+              dom.getAttr('html', 'lang') === 'db-LB') {
+            return window.Gobbledygook(msgid);
+          }
+              
           if (window.json_locale_data && json_locale_data["client"]) {
-          var dict = json_locale_data["client"];
+            var dict = json_locale_data["client"];
             if (dict[msgid] && dict[msgid].length >= 2 &&
                 dict[msgid][1].trim() != "") {
               return dict[msgid][1];
             }
-        }
-        return msgid;
+          }
+          return msgid;
         },
         // See lib/i18n.js format docs
         format: function (fmt, obj, named) {
diff --git a/resources/static/common/js/lib/gobbledygook.js b/resources/static/common/js/lib/gobbledygook.js
new file mode 120000
index 0000000000000000000000000000000000000000..1403b642aef4fd46ccca2f5868826d9027e9f02f
--- /dev/null
+++ b/resources/static/common/js/lib/gobbledygook.js
@@ -0,0 +1 @@
+../../../../../node_modules/gobbledygook/gobbledygook.js
\ No newline at end of file
diff --git a/resources/views/test.ejs b/resources/views/test.ejs
index 6cdbd8907f1af81db029b244cf4a28f1f33c3139..7573f5a20438dbd4d3c88276db03c900efa14ed7 100644
--- a/resources/views/test.ejs
+++ b/resources/views/test.ejs
@@ -69,12 +69,12 @@
     <script src="/common/js/lib/jquery-1.7.1.min.js"></script>
     <script src="/common/js/lib/underscore.js"></script>
     <script src="/common/js/lib/ejs.js"></script>
-    <script src="/i18n/en_US/client.json"></script>
+    <script src="/common/js/lib/gobbledygook.js"></script>
     <script src="/common/js/javascript-extensions.js"></script>
-    <script src="/common/js/gettext.js"></script>
     <script src="/common/js/lib/bidbundle.js"></script>
     <script src="http://testmob.org/include.js"></script>
     <script src="/common/js/browserid.js"></script>
+    <script src="/common/js/gettext.js"></script>
     <script src="/common/js/lib/dom-jquery.js"></script>
     <script src="/common/js/lib/hub.js"></script>
     <script src="/common/js/lib/module.js"></script>
diff --git a/tests/i18n-tests.js b/tests/i18n-tests.js
index 88287ec5d0ebed4d9fb3ec04b7a8134999a6ccfe..b2abd422e1db1b2f7b9546155da1a4a1615f4caa 100755
--- a/tests/i18n-tests.js
+++ b/tests/i18n-tests.js
@@ -8,16 +8,22 @@ require('./lib/test_env.js');
 
 const assert = require('assert'),
       vows = require('vows'),
-      i18n = require('../lib/i18n');
+      i18n = require('../lib/i18n'),
+      start_stop = require('./lib/start-stop.js'),
+      wsapi = require('./lib/wsapi.js'),
+      http = require('http'),
+      path = require('path');
 
 var suite = vows.describe('i18n');
 
+suite.options.error = false;
+
 suite.addBatch({
   "format a string with place values": {
     topic: function () {
       return i18n.format("%s %s!", ["Hello", "World"]);
     },
-    "was interpolated": function (err, str) {
+    "was interpolated": function (str) {
       assert.equal(str, "Hello World!");
     }
   }
@@ -29,7 +35,7 @@ suite.addBatch({
       var params = { salutation: "Hello", place: "World" };
       return i18n.format("%(salutation) %(place)!", params);
     },
-    "was interpolated": function (err, str) {
+    "was interpolated": function (str) {
       assert.equal(str, "Hello World!");
     }
   }
@@ -40,7 +46,7 @@ suite.addBatch({
     topic: function () {
       return i18n.format("Hello World!");
     },
-    "was interpolated": function (err, str) {
+    "was interpolated": function (str) {
       assert.equal(str, "Hello World!");
     }
   },
@@ -48,7 +54,7 @@ suite.addBatch({
     topic: function () {
       return i18n.format(null);
     },
-    "was interpolated": function (err, str) {
+    "was interpolated": function (str) {
       assert.equal(str, "");
     }
   }
@@ -64,7 +70,7 @@ suite.addBatch({
           i18n.parseAcceptLanguage(accept),
           supported, def);
     },
-    "For Punjabi": function (err, locale) {
+    "For Punjabi": function (locale) {
       assert.equal(locale, "pa");
     }
   },
@@ -77,7 +83,7 @@ suite.addBatch({
           i18n.parseAcceptLanguage(accept),
           supported, def);
     },
-    "For Punjabi (India) serve Punjabi": function (err, locale) {
+    "For Punjabi (India) serve Punjabi": function (locale) {
       assert.equal(locale, "pa");
     }
   },
@@ -90,12 +96,97 @@ suite.addBatch({
           i18n.parseAcceptLanguage(accept),
           supported, def);
     },
-    "Don't choose Punjabi (India)": function (err, locale) {
+    "Don't choose Punjabi (India)": function (locale) {
       assert.equal(locale, "en-us");
     }
   }
 });
 
+// point to test translation files
+process.env['TRANSLATION_DIR'] = path.join(__dirname, "i18n_test_files");
+
+// supported languages for the purposes of this test
+process.env['SUPPORTED_LANGUAGES'] = 'en,bg,it-CH';
+
+// now let's start up our servers
+start_stop.addStartupBatches(suite);
+
+function getTestTemplate(langs, tp) {
+  tp = tp || '/i18n_test';
+  return function() {
+    var self = this;
+    var req = http.request({
+      host: '127.0.0.1',
+      port: 10002,
+      path: tp,
+      method: "GET",
+      headers: { 'Accept-Language': langs }
+    }, function (res) {
+      var body = "";
+      res.on('data', function(chunk) { body += chunk; })
+        .on('end', function() {
+          self.callback(null, { code: res.statusCode, body: body });
+        });
+    }).on('error', function (e) {
+      self.callback(e);
+    });
+    req.end();
+  };
+}
+
+suite.addBatch({
+  // test default language
+  "test template with no headers": {
+    topic: getTestTemplate(undefined),
+    "returns english" : function(err, r) {
+      assert.strictEqual(r.code, 200);
+      assert.strictEqual(
+        r.body.trim(),
+        'This is a translation <strong>test</strong> string.');
+    }
+  },
+  // test un-supported case
+  "test template with german headers": {
+    topic: getTestTemplate('de'),
+    "returns english" : function(err, r) {
+      assert.strictEqual(200, r.code);
+      assert.strictEqual(
+        r.body.trim(),
+        'This is a translation <strong>test</strong> string.');
+    }
+  },
+  // test debug translation
+  "test template with debug headers": {
+    topic: getTestTemplate('it-CH'),
+    "returns gobbledygook" : function(err, r) {
+      assert.strictEqual(200, r.code);
+      assert.strictEqual(
+        r.body.trim(),
+        '.ƃuıɹʇs <strong>ʇsǝʇ</strong> uoıʇaʅsuaɹʇ a sı sıɥ⊥');
+    }
+  },
+  // test .json extraction
+  "bulgarian accept headers": {
+    topic: getTestTemplate('bg'),
+    "return a translation extacted from .json file" : function(err, r) {
+      assert.strictEqual(200, r.code);
+      assert.strictEqual(r.body.trim(), "Прова?  Прова?  Четери, пет, шещ?");
+    }
+  },
+  // test .json extraction fallback when translation is the empty string
+  "bulgarian accept headers without a translation": {
+    topic: getTestTemplate('bg', '/i18n_fallback_test'),
+    "return a non-translated string" : function(err, r) {
+      assert.strictEqual(200, r.code);
+      assert.strictEqual(r.body.trim(), "This is not translated");
+    }
+  }
+
+});
+
+// and let's stop them servers
+start_stop.addShutdownBatches(suite);
+
 // run or export the suite.
 if (process.argv[1] === __filename) suite.run();
 else suite.export(module);
diff --git a/tests/i18n_test_files/bg/client.json b/tests/i18n_test_files/bg/client.json
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/i18n_test_files/bg/messages.json b/tests/i18n_test_files/bg/messages.json
new file mode 100644
index 0000000000000000000000000000000000000000..95da66000168b39db20e0979b294af25f8f7c348
--- /dev/null
+++ b/tests/i18n_test_files/bg/messages.json
@@ -0,0 +1,12 @@
+var json_locale_data = {
+  messages: {
+    "This is a translation <strong>test</strong> string.": [
+      null,
+      "Прова?  Прова?  Четери, пет, шещ?"
+    ],
+    "This is not translated": [
+      null,
+      ""
+    ]
+  }
+};
diff --git a/tests/i18n_test_templates/i18n_fallback_test.ejs b/tests/i18n_test_templates/i18n_fallback_test.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..10015bc61850a24da9f0489395444642f581b833
--- /dev/null
+++ b/tests/i18n_test_templates/i18n_fallback_test.ejs
@@ -0,0 +1 @@
+<%- gettext("This is not translated") %>
diff --git a/tests/i18n_test_templates/i18n_test.ejs b/tests/i18n_test_templates/i18n_test.ejs
new file mode 100644
index 0000000000000000000000000000000000000000..fcec0740e9970d17cebf07224cd7bf4d1918bd47
--- /dev/null
+++ b/tests/i18n_test_templates/i18n_test.ejs
@@ -0,0 +1 @@
+<%- gettext("This is a translation <strong>test</strong> string.") %>
diff --git a/tests/static-resource-test.js b/tests/static-resource-test.js
index 76307a47acec4dcc7023601ac53bdff5055bbc8c..8a91b133109db90b3c8a4414c105a5ddd234b66d 100755
--- a/tests/static-resource-test.js
+++ b/tests/static-resource-test.js
@@ -57,7 +57,7 @@ suite.addBatch({
       });
       // Fragile - filename with :locale...
       // When fixing this test case... console.log(res[Object.keys(res)[0]]);
-      var localeIndex = 8;
+      var localeIndex = 9;
       assert.notEqual(files[minFile][localeIndex],
                       res[minRes][localeIndex]);
       var counter = 0;