diff --git a/src/lib/app-state-hoc.jsx b/src/lib/app-state-hoc.jsx
index 20e2852a131c5ad70a878d4d0d4b3038df6c8548..2f70b44d251f52d5ccec5fb08ae6eb786bf79794 100644
--- a/src/lib/app-state-hoc.jsx
+++ b/src/lib/app-state-hoc.jsx
@@ -9,6 +9,9 @@ import localesReducer, {initLocale, localesInitialState} from '../reducers/local
 
 import {setPlayer, setFullScreen} from '../reducers/mode.js';
 
+import locales from 'scratch-l10n';
+import {detectLocale} from './detect-locale';
+
 import {ScratchPaintReducer} from 'scratch-paint';
 
 const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
@@ -33,9 +36,8 @@ const AppStateHOC = function (WrappedComponent) {
             }
 
             let initializedLocales = localesInitialState;
-            if (window.location.search.indexOf('locale=') !== -1 ||
-                window.location.search.indexOf('lang=') !== -1) {
-                const locale = window.location.search.match(/(?:locale|lang)=([\w]+)/)[1];
+            const locale = detectLocale(Object.keys(locales));
+            if (locale !== 'en') {
                 initializedLocales = initLocale(initializedLocales, locale);
             }
 
diff --git a/src/lib/detect-locale.js b/src/lib/detect-locale.js
new file mode 100644
index 0000000000000000000000000000000000000000..c9eaf5feccd6fb362f12288c1ac712029563140b
--- /dev/null
+++ b/src/lib/detect-locale.js
@@ -0,0 +1,38 @@
+/**
+ * @fileoverview
+ * Utility function to detect locale from the browser setting or paramenter on the URL.
+ */
+
+/**
+ * look for language setting in the browser. Check against supported locales.
+ * If there's a parameter in the URL, override the browser setting
+ * @param {Array.string} supportedLocales An array of supported locale codes.
+ * @return {string} the preferred locale
+ */
+const detectLocale = supportedLocales => {
+    let locale = 'en'; // default
+    let browserLocale = window.navigator.userLanguage || window.navigator.language;
+    browserLocale = browserLocale.toLowerCase();
+    // try to set locale from browserLocale
+    if (supportedLocales.includes(browserLocale)) {
+        locale = browserLocale;
+    } else {
+        browserLocale = browserLocale.split('-')[0];
+        if (supportedLocales.includes(browserLocale)) {
+            locale = browserLocale;
+        }
+    }
+
+    if (window.location.search.indexOf('locale=') !== -1 ||
+        window.location.search.indexOf('lang=') !== -1) {
+        const urlLocale = window.location.search.match(/(?:locale|lang)=([\w-]+)/)[1].toLowerCase();
+        if (supportedLocales.includes(urlLocale)) {
+            locale = urlLocale;
+        }
+    }
+    return locale;
+};
+
+export {
+    detectLocale
+};
diff --git a/test/unit/util/detect-locale.test.js b/test/unit/util/detect-locale.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..a7a699c414900b4a42c9b0cf452ac95f06bc65e0
--- /dev/null
+++ b/test/unit/util/detect-locale.test.js
@@ -0,0 +1,70 @@
+import {detectLocale} from '../../../src/lib/detect-locale.js';
+
+const supportedLocales = ['en', 'es', 'pt-br', 'de', 'it'];
+
+Object.defineProperty(window.location,
+    'search',
+    {value: '?name=val', configurable: true}
+);
+Object.defineProperty(window.navigator,
+    'language',
+    {value: 'en-US', configurable: true}
+);
+
+describe('detectLocale', () => {
+    test('uses locale from the URL when present', () => {
+        Object.defineProperty(window.location,
+            'search',
+            {value: '?locale=pt-br'}
+        );
+        expect(detectLocale(supportedLocales)).toEqual('pt-br');
+    });
+
+    test('is case insensitive', () => {
+        Object.defineProperty(window.location,
+            'search',
+            {value: '?locale=pt-BR'}
+        );
+        expect(detectLocale(supportedLocales)).toEqual('pt-br');
+    });
+
+    test('also accepts lang from the URL when present', () => {
+        Object.defineProperty(window.location,
+            'search',
+            {value: '?lang=it'}
+        );
+        expect(detectLocale(supportedLocales)).toEqual('it');
+    });
+
+    test('ignores unsupported locales', () => {
+        Object.defineProperty(window.location,
+            'search',
+            {value: '?lang=sv'}
+        );
+        expect(detectLocale(supportedLocales)).toEqual('en');
+    });
+
+    test('ignores other parameters', () => {
+        Object.defineProperty(window.location,
+            'search',
+            {value: '?enable=language'}
+        );
+        expect(detectLocale(supportedLocales)).toEqual('en');
+    });
+
+    test('uses navigator language property for default if supported', () => {
+        Object.defineProperty(window.navigator,
+            'language',
+            {value: 'pt-BR'}
+        );
+        expect(detectLocale(supportedLocales)).toEqual('pt-br');
+    });
+
+    test('ignores navigator language property if unsupported', () => {
+        Object.defineProperty(window.navigator,
+            'language',
+            {value: 'da'}
+        );
+        expect(detectLocale(supportedLocales)).toEqual('en');
+    });
+});