john-bodley closed pull request #6222: Integrate translation module 
@superset-ui/translation
URL: https://github.com/apache/incubator-superset/pull/6222
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index baee1bc5aa..6104c12ba1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -394,10 +394,10 @@ from flask_babel import lazy_gettext as _
 then wrap our translatable strings with it, e.g. `_('Translate me')`. During 
extraction, string literals passed to `_` will be added to the generated `.po` 
file for each language for later translation.
 At runtime, the `_` function will return the translation of the given string 
for the current language, or the given string itself if no translation is 
available.
 
-In JavaScript, the technique is similar: we import `t` (simple translation), 
`tn` (translation containing a number), and `TCT` (translating entire React 
Components).
+In JavaScript, the technique is similar: we import `t` (simple translation), 
`tn` (translation containing a number).
 
 ```javascript
-import {t, tn, TCT} from locales;
+import {t, tn } from '@superset-ui/translation';
 ```
 
 ### Enabling language selection
diff --git a/superset/assets/package.json b/superset/assets/package.json
index 8293e96e45..581af12433 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -52,6 +52,7 @@
     "@data-ui/theme": "^0.0.62",
     "@data-ui/xy-chart": "^0.0.61",
     "@superset-ui/core": "^0.0.7",
+    "@superset-ui/translation": "^0.2.1",
     "@vx/legend": "^0.0.170",
     "@vx/responsive": "0.0.172",
     "@vx/scale": "^0.0.165",
@@ -78,7 +79,6 @@
     "geojson-extent": "^0.3.2",
     "geolib": "^2.0.24",
     "immutable": "^3.8.2",
-    "jed": "^1.1.1",
     "jquery": "3.1.1",
     "json-bigint": "^0.3.0",
     "lodash": "^4.17.11",
@@ -97,7 +97,6 @@
     "react-bootstrap": "^0.31.5",
     "react-bootstrap-dialog": "^0.10.0",
     "react-bootstrap-slider": "2.1.5",
-    "react-bootstrap-table": "^4.3.1",
     "react-color": "^2.13.8",
     "react-datetime": "^2.14.0",
     "react-dnd": "^2.5.4",
@@ -126,7 +125,6 @@
     "redux-undo": "^1.0.0-beta9-9-7",
     "reselect": "^4.0.0",
     "shortid": "^2.2.6",
-    "sprintf-js": "^1.1.1",
     "srcdoc-polyfill": "^1.0.0",
     "supercluster": "^4.1.1",
     "underscore": "^1.8.3",
diff --git a/superset/assets/spec/helpers/shim.js 
b/superset/assets/spec/helpers/shim.js
index 2884157a4c..a9c2fa91f6 100644
--- a/superset/assets/spec/helpers/shim.js
+++ b/superset/assets/spec/helpers/shim.js
@@ -4,6 +4,7 @@ import 
'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';
 import jsdom from 'jsdom';
 import { configure } from 'enzyme';
 import Adapter from 'enzyme-adapter-react-16';
+import { configure as configureTranslation } from '@superset-ui/translation';
 
 import setupSupersetClient from './setupSupersetClient';
 
@@ -48,4 +49,5 @@ global.window.location = { href: 'about:blank' };
 global.window.performance = { now: () => new Date().getTime() };
 global.$ = require('jquery')(global.window);
 
+configureTranslation();
 setupSupersetClient();
diff --git a/superset/assets/spec/javascripts/modules/utils_spec.jsx 
b/superset/assets/spec/javascripts/modules/utils_spec.jsx
index 46a73ec297..d1566c2dda 100644
--- a/superset/assets/spec/javascripts/modules/utils_spec.jsx
+++ b/superset/assets/spec/javascripts/modules/utils_spec.jsx
@@ -5,7 +5,6 @@ import {
   d3TimeFormatPreset,
   defaultNumberFormatter,
   mainMetric,
-  getClientErrorObject,
 } from '../../../src/modules/utils';
 
 describe('utils', () => {
@@ -98,44 +97,4 @@ describe('utils', () => {
       expect(mainMetric(metrics)).toBe('foo');
     });
   });
-
-  describe('getClientErrorObject', () => {
-    it('Returns a Promise', () => {
-      const response = getClientErrorObject('error');
-      expect(response.constructor === Promise).toBe(true);
-    });
-
-    it('Returns a Promise that resolves to an object with an error key', () => 
{
-      const error = 'error';
-
-      return getClientErrorObject(error).then((errorObj) => {
-        expect(errorObj).toMatchObject({ error });
-      });
-    });
-
-    it('Handles Response that can be parsed as json', () => {
-      const jsonError = { something: 'something', error: 'Error message' };
-      const jsonErrorString = JSON.stringify(jsonError);
-
-      return getClientErrorObject(new 
Response(jsonErrorString)).then((errorObj) => {
-        expect(errorObj).toMatchObject(jsonError);
-      });
-    });
-
-    it('Handles Response that can be parsed as text', () => {
-      const textError = 'Hello I am a text error';
-
-      return getClientErrorObject(new Response(textError)).then((errorObj) => {
-        expect(errorObj).toMatchObject({ error: textError });
-      });
-    });
-
-    it('Handles plain text as input', () => {
-      const error = 'error';
-
-      return getClientErrorObject(error).then((errorObj) => {
-        expect(errorObj).toMatchObject({ error });
-      });
-    });
-  });
 });
diff --git 
a/superset/assets/spec/javascripts/utils/getClientErrorObject_spec.js 
b/superset/assets/spec/javascripts/utils/getClientErrorObject_spec.js
new file mode 100644
index 0000000000..2f05021acb
--- /dev/null
+++ b/superset/assets/spec/javascripts/utils/getClientErrorObject_spec.js
@@ -0,0 +1,41 @@
+import getClientErrorObject from '../../../src/utils/getClientErrorObject';
+
+describe('getClientErrorObject()', () => {
+  it('Returns a Promise', () => {
+    const response = getClientErrorObject('error');
+    expect(response.constructor === Promise).toBe(true);
+  });
+
+  it('Returns a Promise that resolves to an object with an error key', () => {
+    const error = 'error';
+
+    return getClientErrorObject(error).then((errorObj) => {
+      expect(errorObj).toMatchObject({ error });
+    });
+  });
+
+  it('Handles Response that can be parsed as json', () => {
+    const jsonError = { something: 'something', error: 'Error message' };
+    const jsonErrorString = JSON.stringify(jsonError);
+
+    return getClientErrorObject(new Response(jsonErrorString)).then((errorObj) 
=> {
+      expect(errorObj).toMatchObject(jsonError);
+    });
+  });
+
+  it('Handles Response that can be parsed as text', () => {
+    const textError = 'Hello I am a text error';
+
+    return getClientErrorObject(new Response(textError)).then((errorObj) => {
+      expect(errorObj).toMatchObject({ error: textError });
+    });
+  });
+
+  it('Handles plain text as input', () => {
+    const error = 'error';
+
+    return getClientErrorObject(error).then((errorObj) => {
+      expect(errorObj).toMatchObject({ error });
+    });
+  });
+});
diff --git a/superset/assets/src/CRUD/CollectionTable.jsx 
b/superset/assets/src/CRUD/CollectionTable.jsx
index 514f86660e..f38c3d3e5b 100644
--- a/superset/assets/src/CRUD/CollectionTable.jsx
+++ b/superset/assets/src/CRUD/CollectionTable.jsx
@@ -1,14 +1,12 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import shortid from 'shortid';
-
+import { t } from '@superset-ui/translation';
 import Button from '../components/Button';
 import Fieldset from './Fieldset';
 import { recurseReactClone } from './utils';
 import './styles.css';
 
-import { t } from '../locales';
-
 const propTypes = {
   collection: PropTypes.arrayOf(PropTypes.object).isRequired,
   itemGenerator: PropTypes.func,
diff --git a/superset/assets/src/SqlLab/App.jsx 
b/superset/assets/src/SqlLab/App.jsx
index 37be951f67..9e242c3bb0 100644
--- a/superset/assets/src/SqlLab/App.jsx
+++ b/superset/assets/src/SqlLab/App.jsx
@@ -9,13 +9,13 @@ import getInitialState from './getInitialState';
 import rootReducer from './reducers';
 import { initEnhancer } from '../reduxUtils';
 import App from './components/App';
-import { appSetup } from '../common';
+import setupApp from '../setup/setupApp';
 
 import './main.less';
 import '../../stylesheets/reactable-pagination.css';
 import '../components/FilterableTable/FilterableTableStyles.css';
 
-appSetup();
+setupApp();
 
 const appContainer = document.getElementById('app');
 const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
diff --git a/superset/assets/src/SqlLab/actions.js 
b/superset/assets/src/SqlLab/actions.js
index 6d2b6af1a8..e8690145c8 100644
--- a/superset/assets/src/SqlLab/actions.js
+++ b/superset/assets/src/SqlLab/actions.js
@@ -1,15 +1,15 @@
 import shortid from 'shortid';
 import JSONbig from 'json-bigint';
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 
 import { now } from '../modules/dates';
-import { t } from '../locales';
 import {
   addSuccessToast as addSuccessToastAction,
   addDangerToast as addDangerToastAction,
   addInfoToast as addInfoToastAction,
 } from '../messageToasts/actions';
-import { COMMON_ERR_MESSAGES } from '../utils/common';
+import COMMON_ERR_MESSAGES from '../utils/errorMessages';
 
 export const RESET_STATE = 'RESET_STATE';
 export const ADD_QUERY_EDITOR = 'ADD_QUERY_EDITOR';
@@ -153,7 +153,7 @@ export function runQuery(query) {
       .catch((error) => {
         let message = error.error || error.statusText || t('Unknown error');
         if (message.includes('CSRF token')) {
-          message = COMMON_ERR_MESSAGES.SESSION_TIMED_OUT;
+          message = t(COMMON_ERR_MESSAGES.SESSION_TIMED_OUT);
         }
         // @TODO how to verify link?
         dispatch(queryFailed(query, message, error.link));
diff --git a/superset/assets/src/SqlLab/components/ExploreResultsButton.jsx 
b/superset/assets/src/SqlLab/components/ExploreResultsButton.jsx
index 329f731657..06cddbc5f1 100644
--- a/superset/assets/src/SqlLab/components/ExploreResultsButton.jsx
+++ b/superset/assets/src/SqlLab/components/ExploreResultsButton.jsx
@@ -5,12 +5,12 @@ import { bindActionCreators } from 'redux';
 import { connect } from 'react-redux';
 import { Alert } from 'react-bootstrap';
 import Dialog from 'react-bootstrap-dialog';
+import { t } from '@superset-ui/translation';
 
 import shortid from 'shortid';
 import { exportChart } from '../../explore/exploreUtils';
 import * as actions from '../actions';
 import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
-import { t } from '../../locales';
 import Button from '../../components/Button';
 
 const propTypes = {
diff --git a/superset/assets/src/SqlLab/components/HighlightedSql.jsx 
b/superset/assets/src/SqlLab/components/HighlightedSql.jsx
index 119e8d3b53..0cce92dc62 100644
--- a/superset/assets/src/SqlLab/components/HighlightedSql.jsx
+++ b/superset/assets/src/SqlLab/components/HighlightedSql.jsx
@@ -1,12 +1,11 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-
 import SyntaxHighlighter, { registerLanguage } from 
'react-syntax-highlighter/dist/light';
 import sql from 'react-syntax-highlighter/dist/languages/hljs/sql';
 import github from 'react-syntax-highlighter/dist/styles/hljs/github';
+import { t } from '@superset-ui/translation';
 
 import ModalTrigger from '../../components/ModalTrigger';
-import { t } from '../../locales';
 
 registerLanguage('sql', sql);
 
diff --git a/superset/assets/src/SqlLab/components/QueryHistory.jsx 
b/superset/assets/src/SqlLab/components/QueryHistory.jsx
index 442bc4c41a..2c246e4e80 100644
--- a/superset/assets/src/SqlLab/components/QueryHistory.jsx
+++ b/superset/assets/src/SqlLab/components/QueryHistory.jsx
@@ -1,9 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Alert } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 
 import QueryTable from './QueryTable';
-import { t } from '../../locales';
 
 const propTypes = {
   queries: PropTypes.array.isRequired,
diff --git a/superset/assets/src/SqlLab/components/QuerySearch.jsx 
b/superset/assets/src/SqlLab/components/QuerySearch.jsx
index 9e920295a0..62d02556cc 100644
--- a/superset/assets/src/SqlLab/components/QuerySearch.jsx
+++ b/superset/assets/src/SqlLab/components/QuerySearch.jsx
@@ -2,6 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Button } from 'react-bootstrap';
 import Select from 'react-select';
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 
 import Loading from '../../components/Loading';
@@ -14,7 +15,6 @@ import {
 } from '../../modules/dates';
 import { STATUS_OPTIONS, TIME_OPTIONS } from '../constants';
 import AsyncSelect from '../../components/AsyncSelect';
-import { t } from '../../locales';
 
 const propTypes = {
   actions: PropTypes.object.isRequired,
diff --git a/superset/assets/src/SqlLab/components/QueryTable.jsx 
b/superset/assets/src/SqlLab/components/QueryTable.jsx
index 9e539636e1..c4dc6c8373 100644
--- a/superset/assets/src/SqlLab/components/QueryTable.jsx
+++ b/superset/assets/src/SqlLab/components/QueryTable.jsx
@@ -1,9 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-
 import moment from 'moment';
 import { Table } from 'reactable';
 import { Label, ProgressBar, Well } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
+
 import Link from './Link';
 import ResultSet from './ResultSet';
 import ModalTrigger from '../../components/ModalTrigger';
@@ -11,7 +12,6 @@ import HighlightedSql from './HighlightedSql';
 import { fDuration } from '../../modules/dates';
 import { storeQuery } from '../../utils/common';
 import QueryStateLabel from './QueryStateLabel';
-import { t } from '../../locales';
 
 const propTypes = {
   columns: PropTypes.array,
diff --git a/superset/assets/src/SqlLab/components/ResultSet.jsx 
b/superset/assets/src/SqlLab/components/ResultSet.jsx
index 6fa820057c..6b482e4312 100644
--- a/superset/assets/src/SqlLab/components/ResultSet.jsx
+++ b/superset/assets/src/SqlLab/components/ResultSet.jsx
@@ -2,13 +2,13 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Alert, Button, ButtonGroup, ProgressBar } from 'react-bootstrap';
 import shortid from 'shortid';
+import { t } from '@superset-ui/translation';
 
 import Loading from '../../components/Loading';
 import ExploreResultsButton from './ExploreResultsButton';
 import HighlightedSql from './HighlightedSql';
 import FilterableTable from '../../components/FilterableTable/FilterableTable';
 import QueryStateLabel from './QueryStateLabel';
-import { t } from '../../locales';
 
 const propTypes = {
   actions: PropTypes.object,
diff --git a/superset/assets/src/SqlLab/components/RunQueryActionButton.jsx 
b/superset/assets/src/SqlLab/components/RunQueryActionButton.jsx
index dd1d8b0250..a5b8daed46 100644
--- a/superset/assets/src/SqlLab/components/RunQueryActionButton.jsx
+++ b/superset/assets/src/SqlLab/components/RunQueryActionButton.jsx
@@ -1,7 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import { t } from '@superset-ui/translation';
+
 import Button from '../../components/Button';
-import { t } from '../../locales';
 
 const propTypes = {
   allowAsync: PropTypes.bool.isRequired,
diff --git a/superset/assets/src/SqlLab/components/SaveQuery.jsx 
b/superset/assets/src/SqlLab/components/SaveQuery.jsx
index 007fd57bd0..8b2a28ca66 100644
--- a/superset/assets/src/SqlLab/components/SaveQuery.jsx
+++ b/superset/assets/src/SqlLab/components/SaveQuery.jsx
@@ -1,10 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { FormControl, FormGroup, Row, Col } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 
 import Button from '../../components/Button';
 import ModalTrigger from '../../components/ModalTrigger';
-import { t } from '../../locales';
 
 const propTypes = {
   defaultLabel: PropTypes.string,
diff --git a/superset/assets/src/SqlLab/components/ShareSqlLabQuery.jsx 
b/superset/assets/src/SqlLab/components/ShareSqlLabQuery.jsx
index 55f8c8820d..f59129b29a 100644
--- a/superset/assets/src/SqlLab/components/ShareSqlLabQuery.jsx
+++ b/superset/assets/src/SqlLab/components/ShareSqlLabQuery.jsx
@@ -1,12 +1,12 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Popover, OverlayTrigger } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 
 import Button from '../../components/Button';
 import CopyToClipboard from '../../components/CopyToClipboard';
 import { storeQuery } from '../../utils/common';
-import { getClientErrorObject } from '../../modules/utils';
-import { t } from '../../locales';
+import getClientErrorObject from '../../utils/getClientErrorObject';
 import withToasts from '../../messageToasts/enhancers/withToasts';
 
 const propTypes = {
diff --git a/superset/assets/src/SqlLab/components/SouthPane.jsx 
b/superset/assets/src/SqlLab/components/SouthPane.jsx
index ce81c8dfb1..eddc808231 100644
--- a/superset/assets/src/SqlLab/components/SouthPane.jsx
+++ b/superset/assets/src/SqlLab/components/SouthPane.jsx
@@ -4,12 +4,12 @@ import shortid from 'shortid';
 import { Alert, Label, Tab, Tabs } from 'react-bootstrap';
 import { connect } from 'react-redux';
 import { bindActionCreators } from 'redux';
+import { t } from '@superset-ui/translation';
 
 import * as Actions from '../actions';
 import QueryHistory from './QueryHistory';
 import ResultSet from './ResultSet';
 import { STATUS_OPTIONS, STATE_BSSTYLE_MAP } from '../constants';
-import { t } from '../../locales';
 
 /*
     editorQueries are queries executed by users passed from SqlEditor component
diff --git a/superset/assets/src/SqlLab/components/SqlEditor.jsx 
b/superset/assets/src/SqlLab/components/SqlEditor.jsx
index 7f82e02a6f..e739efa518 100644
--- a/superset/assets/src/SqlLab/components/SqlEditor.jsx
+++ b/superset/assets/src/SqlLab/components/SqlEditor.jsx
@@ -14,6 +14,7 @@ import {
   Collapse,
 } from 'react-bootstrap';
 import SplitPane from 'react-split-pane';
+import { t } from '@superset-ui/translation';
 
 import Button from '../../components/Button';
 import TemplateParamsEditor from './TemplateParamsEditor';
@@ -26,8 +27,6 @@ import SqlEditorLeftBar from './SqlEditorLeftBar';
 import AceEditorWrapper from './AceEditorWrapper';
 import { STATE_BSSTYLE_MAP } from '../constants';
 import RunQueryActionButton from './RunQueryActionButton';
-import { t } from '../../locales';
-
 
 const propTypes = {
   actions: PropTypes.object.isRequired,
diff --git a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx 
b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx
index 12530eb375..f0b4d1a19f 100644
--- a/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx
+++ b/superset/assets/src/SqlLab/components/SqlEditorLeftBar.jsx
@@ -4,12 +4,12 @@ import { ControlLabel, Button } from 'react-bootstrap';
 import { connect } from 'react-redux';
 import Select from 'react-virtualized-select';
 import createFilterOptions from 'react-select-fast-filter-options';
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 
 import TableElement from './TableElement';
 import AsyncSelect from '../../components/AsyncSelect';
 import RefreshLabel from '../../components/RefreshLabel';
-import { t } from '../../locales';
 
 const propTypes = {
   queryEditor: PropTypes.object.isRequired,
diff --git a/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx 
b/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
index 9bf3978592..43b73546cd 100644
--- a/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
+++ b/superset/assets/src/SqlLab/components/TabbedSqlEditors.jsx
@@ -4,11 +4,11 @@ import { DropdownButton, MenuItem, Tab, Tabs } from 
'react-bootstrap';
 import { connect } from 'react-redux';
 import { bindActionCreators } from 'redux';
 import URI from 'urijs';
+import { t } from '@superset-ui/translation';
 
 import * as Actions from '../actions';
 import SqlEditor from './SqlEditor';
 import { areArraysShallowEqual } from '../../reduxUtils';
-import { t } from '../../locales';
 import TabStatusIcon from './TabStatusIcon';
 
 const propTypes = {
diff --git a/superset/assets/src/SqlLab/components/TableElement.jsx 
b/superset/assets/src/SqlLab/components/TableElement.jsx
index 96fae9b0b3..d87d7e5db1 100644
--- a/superset/assets/src/SqlLab/components/TableElement.jsx
+++ b/superset/assets/src/SqlLab/components/TableElement.jsx
@@ -1,15 +1,14 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-
 import { ButtonGroup, Collapse, Well } from 'react-bootstrap';
 import shortid from 'shortid';
+import { t } from '@superset-ui/translation';
 
 import CopyToClipboard from '../../components/CopyToClipboard';
 import Link from './Link';
 import ColumnElement from './ColumnElement';
 import ModalTrigger from '../../components/ModalTrigger';
 import Loading from '../../components/Loading';
-import { t } from '../../locales';
 
 const propTypes = {
   table: PropTypes.object,
diff --git a/superset/assets/src/SqlLab/components/TemplateParamsEditor.jsx 
b/superset/assets/src/SqlLab/components/TemplateParamsEditor.jsx
index 8a3387ad54..baed940920 100644
--- a/superset/assets/src/SqlLab/components/TemplateParamsEditor.jsx
+++ b/superset/assets/src/SqlLab/components/TemplateParamsEditor.jsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Badge } from 'react-bootstrap';
-
 import AceEditor from 'react-ace';
 import 'brace/mode/sql';
 import 'brace/mode/json';
@@ -9,10 +8,11 @@ import 'brace/mode/html';
 import 'brace/mode/markdown';
 import 'brace/theme/textmate';
 
+import { t } from '@superset-ui/translation';
+
 import ModalTrigger from '../../components/ModalTrigger';
 import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
 import Button from '../../components/Button';
-import { t } from '../../locales';
 
 const propTypes = {
   onChange: PropTypes.func,
diff --git a/superset/assets/src/SqlLab/getInitialState.js 
b/superset/assets/src/SqlLab/getInitialState.js
index aa98bf1774..ba984c2b9c 100644
--- a/superset/assets/src/SqlLab/getInitialState.js
+++ b/superset/assets/src/SqlLab/getInitialState.js
@@ -1,5 +1,5 @@
 import shortid from 'shortid';
-import { t } from '../locales';
+import { t } from '@superset-ui/translation';
 import getToastsFromPyFlashMessages from 
'../messageToasts/utils/getToastsFromPyFlashMessages';
 
 export default function getInitialState({ defaultDbId, ...restBootstrapData }) 
{
diff --git a/superset/assets/src/SqlLab/reducers.js 
b/superset/assets/src/SqlLab/reducers.js
index e1640a5426..87d033251a 100644
--- a/superset/assets/src/SqlLab/reducers.js
+++ b/superset/assets/src/SqlLab/reducers.js
@@ -1,7 +1,8 @@
 import { combineReducers } from 'redux';
 import shortid from 'shortid';
-import messageToasts from '../messageToasts/reducers';
+import { t } from '@superset-ui/translation';
 
+import messageToasts from '../messageToasts/reducers';
 import getInitialState from './getInitialState';
 import * as actions from './actions';
 import { now } from '../modules/dates';
@@ -13,7 +14,6 @@ import {
   getFromArr,
   addToArr,
 } from '../reduxUtils';
-import { t } from '../locales';
 
 export const sqlLabReducer = function (state = {}, action) {
   const actionHandlers = {
diff --git a/superset/assets/src/addSlice/AddSliceContainer.jsx 
b/superset/assets/src/addSlice/AddSliceContainer.jsx
index 0d876af537..f46dbadfee 100644
--- a/superset/assets/src/addSlice/AddSliceContainer.jsx
+++ b/superset/assets/src/addSlice/AddSliceContainer.jsx
@@ -2,8 +2,8 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Button, Panel } from 'react-bootstrap';
 import Select from 'react-virtualized-select';
+import { t } from '@superset-ui/translation';
 import visTypes from '../explore/visTypes';
-import { t } from '../locales';
 
 const propTypes = {
   datasources: PropTypes.arrayOf(PropTypes.shape({
diff --git a/superset/assets/src/addSlice/App.jsx 
b/superset/assets/src/addSlice/App.jsx
index 56469cc1d9..34154a9b21 100644
--- a/superset/assets/src/addSlice/App.jsx
+++ b/superset/assets/src/addSlice/App.jsx
@@ -1,9 +1,9 @@
 import React from 'react';
 import { hot } from 'react-hot-loader';
-import { appSetup } from '../common';
+import setupApp from '../setup/setupApp';
 import AddSliceContainer from './AddSliceContainer';
 
-appSetup();
+setupApp();
 
 const addSliceContainer = document.getElementById('js-add-slice-container');
 const bootstrapData = 
JSON.parse(addSliceContainer.getAttribute('data-bootstrap'));
diff --git a/superset/assets/src/chart/chartAction.js 
b/superset/assets/src/chart/chartAction.js
index 2898740818..92bb8028fd 100644
--- a/superset/assets/src/chart/chartAction.js
+++ b/superset/assets/src/chart/chartAction.js
@@ -1,13 +1,13 @@
 /* global window, AbortController */
 /* eslint no-undef: 'error' */
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 import { getExploreUrlAndPayload, getAnnotationJsonUrl } from 
'../explore/exploreUtils';
 import { requiresQuery, ANNOTATION_SOURCE_TYPES } from 
'../modules/AnnotationTypes';
 import { addDangerToast } from '../messageToasts/actions';
 import { Logger, LOG_ACTIONS_LOAD_CHART } from '../logger';
-import { getClientErrorObject } from '../modules/utils';
 import { TIME_RANGE_SEPARATOR } from '../utils/common';
-import { t } from '../locales';
+import getClientErrorObject from '../utils/getClientErrorObject';
 
 export const CHART_UPDATE_STARTED = 'CHART_UPDATE_STARTED';
 export function chartUpdateStarted(queryController, latestQueryFormData, key) {
diff --git a/superset/assets/src/chart/chartReducer.js 
b/superset/assets/src/chart/chartReducer.js
index 65df6e5082..f6b608cca6 100644
--- a/superset/assets/src/chart/chartReducer.js
+++ b/superset/assets/src/chart/chartReducer.js
@@ -1,7 +1,7 @@
 /* eslint camelcase: 0 */
+import { t } from '@superset-ui/translation';
 import { now } from '../modules/dates';
 import * as actions from './chartAction';
-import { t } from '../locales';
 
 export const chart = {
   id: 0,
diff --git a/superset/assets/src/common.js b/superset/assets/src/common.js
deleted file mode 100644
index 2dc0488479..0000000000
--- a/superset/assets/src/common.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/* eslint global-require: 0 */
-import $ from 'jquery';
-import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';
-import { SupersetClient } from '@superset-ui/core';
-import { toggleCheckbox } from './modules/utils';
-import setupClient from './setup/setupClient';
-import setupColors from './setup/setupColors';
-import setupPlugins from './setup/setupPlugins';
-
-setupColors();
-setupPlugins();
-
-$(document).ready(function () {
-  $(':checkbox[data-checkbox-api-prefix]').change(function () {
-    const $this = $(this);
-    const prefix = $this.data('checkbox-api-prefix');
-    const id = $this.attr('id');
-    toggleCheckbox(prefix, '#' + id);
-  });
-
-  // for language picker dropdown
-  $('#language-picker a').click(function (ev) {
-    ev.preventDefault();
-    SupersetClient.get({
-      endpoint: ev.currentTarget.getAttribute('href'),
-      parseMethod: null,
-    })
-      .then(() => {
-        location.reload();
-      });
-  });
-});
-
-export function appSetup() {
-  setupClient();
-
-  // A set of hacks to allow apps to run within a FAB template
-  // this allows for the server side generated menus to function
-  window.$ = $;
-  window.jQuery = $;
-  require('bootstrap');
-}
diff --git a/superset/assets/src/components/AlteredSliceTag.jsx 
b/superset/assets/src/components/AlteredSliceTag.jsx
index a0c1f1ee52..bfde6f62bf 100644
--- a/superset/assets/src/components/AlteredSliceTag.jsx
+++ b/superset/assets/src/components/AlteredSliceTag.jsx
@@ -2,11 +2,10 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Table, Tr, Td, Thead, Th } from 'reactable';
 import { isEqual, isEmpty } from 'lodash';
-
+import { t } from '@superset-ui/translation';
 import TooltipWrapper from './TooltipWrapper';
 import { controls } from '../explore/controls';
 import ModalTrigger from './ModalTrigger';
-import { t } from '../locales';
 
 const propTypes = {
   origFormData: PropTypes.object.isRequired,
diff --git a/superset/assets/src/components/AsyncSelect.jsx 
b/superset/assets/src/components/AsyncSelect.jsx
index 4c2ae814b6..579535f6cf 100644
--- a/superset/assets/src/components/AsyncSelect.jsx
+++ b/superset/assets/src/components/AsyncSelect.jsx
@@ -1,8 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import Select from 'react-select';
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
-import { t } from '../locales';
 
 const propTypes = {
   dataEndpoint: PropTypes.string.isRequired,
diff --git a/superset/assets/src/components/CachedLabel.jsx 
b/superset/assets/src/components/CachedLabel.jsx
index 9bfd03751a..845eac5709 100644
--- a/superset/assets/src/components/CachedLabel.jsx
+++ b/superset/assets/src/components/CachedLabel.jsx
@@ -2,8 +2,8 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Label } from 'react-bootstrap';
 import moment from 'moment';
+import { t } from '@superset-ui/translation';
 import TooltipWrapper from './TooltipWrapper';
-import { t } from '../locales';
 
 const propTypes = {
   onClick: PropTypes.func,
diff --git a/superset/assets/src/components/CopyToClipboard.jsx 
b/superset/assets/src/components/CopyToClipboard.jsx
index c2b9c573e4..199e8d430f 100644
--- a/superset/assets/src/components/CopyToClipboard.jsx
+++ b/superset/assets/src/components/CopyToClipboard.jsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Tooltip, OverlayTrigger, MenuItem } from 'react-bootstrap';
-import { t } from '../locales';
+import { t } from '@superset-ui/translation';
 
 const propTypes = {
   copyNode: PropTypes.node,
diff --git a/superset/assets/src/components/EditableTitle.jsx 
b/superset/assets/src/components/EditableTitle.jsx
index a3df712001..4d71ac7fd1 100644
--- a/superset/assets/src/components/EditableTitle.jsx
+++ b/superset/assets/src/components/EditableTitle.jsx
@@ -1,8 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
+import { t } from '@superset-ui/translation';
 import TooltipWrapper from './TooltipWrapper';
-import { t } from '../locales';
 
 const propTypes = {
   title: PropTypes.string,
diff --git a/superset/assets/src/components/FaveStar.jsx 
b/superset/assets/src/components/FaveStar.jsx
index 60de9d1439..cba83db6d4 100644
--- a/superset/assets/src/components/FaveStar.jsx
+++ b/superset/assets/src/components/FaveStar.jsx
@@ -1,8 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
+import { t } from '@superset-ui/translation';
 import TooltipWrapper from './TooltipWrapper';
-import { t } from '../locales';
 
 const propTypes = {
   itemId: PropTypes.number.isRequired,
diff --git a/superset/assets/src/components/RefreshChartOverlay.jsx 
b/superset/assets/src/components/RefreshChartOverlay.jsx
index 9e3fced23a..841559a8b2 100644
--- a/superset/assets/src/components/RefreshChartOverlay.jsx
+++ b/superset/assets/src/components/RefreshChartOverlay.jsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import { t } from '@superset-ui/translation';
 import Button from '../components/Button';
-import { t } from '../locales';
 
 const propTypes = {
   height: PropTypes.number.isRequired,
diff --git a/superset/assets/src/components/TableLoader.jsx 
b/superset/assets/src/components/TableLoader.jsx
index 2f57ab80d1..0d31934417 100644
--- a/superset/assets/src/components/TableLoader.jsx
+++ b/superset/assets/src/components/TableLoader.jsx
@@ -1,10 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Table, Tr, Td } from 'reactable';
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 
 import withToasts from '../messageToasts/enhancers/withToasts';
-import { t } from '../locales';
 import Loading from '../components/Loading';
 import '../../stylesheets/reactable-pagination.css';
 
diff --git a/superset/assets/src/components/URLShortLinkButton.jsx 
b/superset/assets/src/components/URLShortLinkButton.jsx
index 47fd3329dc..c2072db8cc 100644
--- a/superset/assets/src/components/URLShortLinkButton.jsx
+++ b/superset/assets/src/components/URLShortLinkButton.jsx
@@ -1,9 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Popover, OverlayTrigger } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 import CopyToClipboard from './CopyToClipboard';
 import { getShortUrl } from '../utils/common';
-import { t } from '../locales';
 import withToasts from '../messageToasts/enhancers/withToasts';
 
 const propTypes = {
diff --git a/superset/assets/src/components/URLShortLinkModal.jsx 
b/superset/assets/src/components/URLShortLinkModal.jsx
index 907b239d6a..1ad4d21663 100644
--- a/superset/assets/src/components/URLShortLinkModal.jsx
+++ b/superset/assets/src/components/URLShortLinkModal.jsx
@@ -1,8 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import { t } from '@superset-ui/translation';
 import CopyToClipboard from './CopyToClipboard';
 import { getShortUrl } from '../utils/common';
-import { t } from '../locales';
 import withToasts from '../messageToasts/enhancers/withToasts';
 import ModalTrigger from './ModalTrigger';
 
diff --git a/superset/assets/src/dashboard/App.jsx 
b/superset/assets/src/dashboard/App.jsx
index a1d8f2ec0a..865803b2bd 100644
--- a/superset/assets/src/dashboard/App.jsx
+++ b/superset/assets/src/dashboard/App.jsx
@@ -6,12 +6,14 @@ import { hot } from 'react-hot-loader';
 
 import { initFeatureFlags } from 'src/featureFlags';
 import { initEnhancer } from '../reduxUtils';
-import { appSetup } from '../common';
+import setupApp from '../setup/setupApp';
+import setupPlugins from '../setup/setupPlugins';
 import DashboardContainer from './containers/Dashboard';
 import getInitialState from './reducers/getInitialState';
 import rootReducer from './reducers/index';
 
-appSetup();
+setupApp();
+setupPlugins();
 
 const appContainer = document.getElementById('app');
 const bootstrapData = JSON.parse(appContainer.getAttribute('data-bootstrap'));
diff --git a/superset/assets/src/dashboard/actions/dashboardState.js 
b/superset/assets/src/dashboard/actions/dashboardState.js
index 2ff139fcdb..6fed016c15 100644
--- a/superset/assets/src/dashboard/actions/dashboardState.js
+++ b/superset/assets/src/dashboard/actions/dashboardState.js
@@ -1,19 +1,19 @@
 /* eslint camelcase: 0 */
 import { ActionCreators as UndoActionCreators } from 'redux-undo';
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 
 import { addChart, removeChart, refreshChart } from '../../chart/chartAction';
 import { chart as initChart } from '../../chart/chartReducer';
 import { fetchDatasourceMetadata } from '../../dashboard/actions/datasources';
 import { applyDefaultFormData } from '../../explore/store';
-import { getClientErrorObject } from '../../modules/utils';
+import getClientErrorObject from '../../utils/getClientErrorObject';
 import {
   Logger,
   LOG_ACTIONS_CHANGE_DASHBOARD_FILTER,
   LOG_ACTIONS_REFRESH_DASHBOARD,
 } from '../../logger';
 import { SAVE_TYPE_OVERWRITE } from '../util/constants';
-import { t } from '../../locales';
 import {
   addSuccessToast,
   addWarningToast,
diff --git a/superset/assets/src/dashboard/actions/datasources.js 
b/superset/assets/src/dashboard/actions/datasources.js
index 85d91bdafd..f5e8a48da9 100644
--- a/superset/assets/src/dashboard/actions/datasources.js
+++ b/superset/assets/src/dashboard/actions/datasources.js
@@ -1,5 +1,5 @@
 import { SupersetClient } from '@superset-ui/core';
-import { getClientErrorObject } from '../../modules/utils';
+import getClientErrorObject from '../../utils/getClientErrorObject';
 
 export const SET_DATASOURCE = 'SET_DATASOURCE';
 export function setDatasource(datasource, key) {
diff --git a/superset/assets/src/dashboard/actions/sliceEntities.js 
b/superset/assets/src/dashboard/actions/sliceEntities.js
index 31a03db0d2..6194327a8c 100644
--- a/superset/assets/src/dashboard/actions/sliceEntities.js
+++ b/superset/assets/src/dashboard/actions/sliceEntities.js
@@ -1,12 +1,10 @@
 /* eslint camelcase: 0 */
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 
 import { addDangerToast } from '../../messageToasts/actions';
-import { t } from '../../locales';
-import {
-  getDatasourceParameter,
-  getClientErrorObject,
-} from '../../modules/utils';
+import { getDatasourceParameter } from '../../modules/utils';
+import getClientErrorObject from '../../utils/getClientErrorObject';
 
 export const SET_ALL_SLICES = 'SET_ALL_SLICES';
 export function setAllSlices(slices) {
diff --git a/superset/assets/src/dashboard/components/AddSliceCard.jsx 
b/superset/assets/src/dashboard/components/AddSliceCard.jsx
index a98546c727..12f72a2db7 100644
--- a/superset/assets/src/dashboard/components/AddSliceCard.jsx
+++ b/superset/assets/src/dashboard/components/AddSliceCard.jsx
@@ -1,7 +1,7 @@
 import cx from 'classnames';
 import React from 'react';
 import PropTypes from 'prop-types';
-import { t } from '../../locales';
+import { t } from '@superset-ui/translation';
 
 const propTypes = {
   datasourceLink: PropTypes.string,
diff --git a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx 
b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx
index 4bfb2bb183..145dcc30a0 100644
--- a/superset/assets/src/dashboard/components/BuilderComponentPane.jsx
+++ b/superset/assets/src/dashboard/components/BuilderComponentPane.jsx
@@ -4,6 +4,7 @@ import React from 'react';
 import cx from 'classnames';
 import { StickyContainer, Sticky } from 'react-sticky';
 import { ParentSize } from '@vx/responsive';
+import { t } from '@superset-ui/translation';
 
 import NewColumn from './gridComponents/new/NewColumn';
 import NewDivider from './gridComponents/new/NewDivider';
@@ -12,7 +13,6 @@ import NewRow from './gridComponents/new/NewRow';
 import NewTabs from './gridComponents/new/NewTabs';
 import NewMarkdown from './gridComponents/new/NewMarkdown';
 import SliceAdder from '../containers/SliceAdder';
-import { t } from '../../locales';
 
 const SUPERSET_HEADER_HEIGHT = 59;
 
diff --git a/superset/assets/src/dashboard/components/CodeModal.jsx 
b/superset/assets/src/dashboard/components/CodeModal.jsx
index cc0c9f2a41..c02f0f5e09 100644
--- a/superset/assets/src/dashboard/components/CodeModal.jsx
+++ b/superset/assets/src/dashboard/components/CodeModal.jsx
@@ -1,8 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import { t } from '@superset-ui/translation';
 
 import ModalTrigger from '../../components/ModalTrigger';
-import { t } from '../../locales';
 
 const propTypes = {
   triggerNode: PropTypes.node.isRequired,
diff --git a/superset/assets/src/dashboard/components/CssEditor.jsx 
b/superset/assets/src/dashboard/components/CssEditor.jsx
index 45ef86d63a..846ee8ab88 100644
--- a/superset/assets/src/dashboard/components/CssEditor.jsx
+++ b/superset/assets/src/dashboard/components/CssEditor.jsx
@@ -1,13 +1,12 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import Select from 'react-select';
-
 import AceEditor from 'react-ace';
 import 'brace/mode/css';
 import 'brace/theme/github';
+import { t } from '@superset-ui/translation';
 
 import ModalTrigger from '../../components/ModalTrigger';
-import { t } from '../../locales';
 
 const propTypes = {
   initialCss: PropTypes.string,
diff --git a/superset/assets/src/dashboard/components/Dashboard.jsx 
b/superset/assets/src/dashboard/components/Dashboard.jsx
index 20c161b6cb..fffcd69dd4 100644
--- a/superset/assets/src/dashboard/components/Dashboard.jsx
+++ b/superset/assets/src/dashboard/components/Dashboard.jsx
@@ -1,5 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import { t } from '@superset-ui/translation';
 
 import getChartIdsFromLayout from '../util/getChartIdsFromLayout';
 import DashboardBuilder from '../containers/DashboardBuilder';
@@ -21,8 +22,6 @@ import {
   LOG_ACTIONS_FIRST_DASHBOARD_LOAD,
 } from '../../logger';
 
-import { t } from '../../locales';
-
 import '../stylesheets/index.less';
 
 const propTypes = {
diff --git a/superset/assets/src/dashboard/components/DeleteComponentModal.jsx 
b/superset/assets/src/dashboard/components/DeleteComponentModal.jsx
index ea7721c037..6ebdef4525 100644
--- a/superset/assets/src/dashboard/components/DeleteComponentModal.jsx
+++ b/superset/assets/src/dashboard/components/DeleteComponentModal.jsx
@@ -1,9 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Button } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 
 import ModalTrigger from '../../components/ModalTrigger';
-import { t } from '../../locales';
 
 const propTypes = {
   triggerNode: PropTypes.node.isRequired,
diff --git a/superset/assets/src/dashboard/components/Header.jsx 
b/superset/assets/src/dashboard/components/Header.jsx
index 1dfe8469a4..9cdafb8009 100644
--- a/superset/assets/src/dashboard/components/Header.jsx
+++ b/superset/assets/src/dashboard/components/Header.jsx
@@ -1,6 +1,7 @@
 /* eslint-env browser */
 import React from 'react';
 import PropTypes from 'prop-types';
+import { t } from '@superset-ui/translation';
 
 import HeaderActionsDropdown from './HeaderActionsDropdown';
 import EditableTitle from '../../components/EditableTitle';
@@ -9,7 +10,6 @@ import FaveStar from '../../components/FaveStar';
 import UndoRedoKeylisteners from './UndoRedoKeylisteners';
 
 import { chartPropShape } from '../util/propShapes';
-import { t } from '../../locales';
 import {
   UNDO_LIMIT,
   SAVE_TYPE_OVERWRITE,
diff --git a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx 
b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
index 9967ce0328..f97d4c1441 100644
--- a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
+++ b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
@@ -2,13 +2,13 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { SupersetClient } from '@superset-ui/core';
 import { DropdownButton, MenuItem } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 
 import CssEditor from './CssEditor';
 import RefreshIntervalModal from './RefreshIntervalModal';
 import SaveModal from './SaveModal';
 import injectCustomCss from '../util/injectCustomCss';
 import { SAVE_TYPE_NEWDASHBOARD } from '../util/constants';
-import { t } from '../../locales';
 import URLShortLinkModal from '../../components/URLShortLinkModal';
 import getDashboardUrl from '../util/getDashboardUrl';
 
diff --git a/superset/assets/src/dashboard/components/MissingChart.jsx 
b/superset/assets/src/dashboard/components/MissingChart.jsx
index c45c445167..56b28000d3 100644
--- a/superset/assets/src/dashboard/components/MissingChart.jsx
+++ b/superset/assets/src/dashboard/components/MissingChart.jsx
@@ -1,8 +1,8 @@
 import PropTypes from 'prop-types';
 import React from 'react';
+import { t } from '@superset-ui/translation';
 
 import Loading from '../../components/Loading';
-import { t } from '../../locales';
 
 const propTypes = {
   height: PropTypes.number.isRequired,
diff --git a/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx 
b/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx
index 1ed4f82bb4..e5d375ae4c 100644
--- a/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx
+++ b/superset/assets/src/dashboard/components/RefreshIntervalModal.jsx
@@ -1,8 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import Select from 'react-select';
+import { t } from '@superset-ui/translation';
+
 import ModalTrigger from '../../components/ModalTrigger';
-import { t } from '../../locales';
 
 const propTypes = {
   triggerNode: PropTypes.node.isRequired,
diff --git a/superset/assets/src/dashboard/components/SaveModal.jsx 
b/superset/assets/src/dashboard/components/SaveModal.jsx
index 2e3d94973f..f726028eaa 100644
--- a/superset/assets/src/dashboard/components/SaveModal.jsx
+++ b/superset/assets/src/dashboard/components/SaveModal.jsx
@@ -1,10 +1,10 @@
 /* eslint-env browser */
 import React from 'react';
 import PropTypes from 'prop-types';
-
 import { Button, FormControl, FormGroup, Radio } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
+
 import ModalTrigger from '../../components/ModalTrigger';
-import { t } from '../../locales';
 import Checkbox from '../../components/Checkbox';
 import { SAVE_TYPE_OVERWRITE, SAVE_TYPE_NEWDASHBOARD } from 
'../util/constants';
 
diff --git a/superset/assets/src/dashboard/components/SliceAdder.jsx 
b/superset/assets/src/dashboard/components/SliceAdder.jsx
index 608cfc15a3..96b87e96c4 100644
--- a/superset/assets/src/dashboard/components/SliceAdder.jsx
+++ b/superset/assets/src/dashboard/components/SliceAdder.jsx
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
 import { DropdownButton, MenuItem } from 'react-bootstrap';
 import { CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';
 import SearchInput, { createFilter } from 'react-search-input';
+import { t } from '@superset-ui/translation';
 
 import AddSliceCard from './AddSliceCard';
 import AddSliceDragPreview from './dnd/AddSliceDragPreview';
@@ -12,7 +13,6 @@ import Loading from '../../components/Loading';
 import { CHART_TYPE, NEW_COMPONENT_SOURCE_TYPE } from '../util/componentTypes';
 import { NEW_CHART_ID, NEW_COMPONENTS_SOURCE_ID } from '../util/constants';
 import { slicePropShape } from '../util/propShapes';
-import { t } from '../../locales';
 
 const propTypes = {
   fetchAllSlices: PropTypes.func.isRequired,
diff --git a/superset/assets/src/dashboard/components/SliceHeader.jsx 
b/superset/assets/src/dashboard/components/SliceHeader.jsx
index 7631ba867e..325b1fc0e7 100644
--- a/superset/assets/src/dashboard/components/SliceHeader.jsx
+++ b/superset/assets/src/dashboard/components/SliceHeader.jsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
-import { t } from '../../locales';
+import { t } from '@superset-ui/translation';
 import EditableTitle from '../../components/EditableTitle';
 import TooltipWrapper from '../../components/TooltipWrapper';
 import SliceHeaderControls from './SliceHeaderControls';
diff --git a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx 
b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
index b5ee0b81df..de412f1680 100644
--- a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
+++ b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
@@ -2,6 +2,8 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import moment from 'moment';
 import { Dropdown, MenuItem } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
+
 import {
   Logger,
   LOG_ACTIONS_EXPLORE_DASHBOARD_CHART,
@@ -9,8 +11,6 @@ import {
   LOG_ACTIONS_REFRESH_CHART,
 } from '../../logger';
 
-import { t } from '../../locales';
-
 const propTypes = {
   slice: PropTypes.object.isRequired,
   isCached: PropTypes.bool,
diff --git 
a/superset/assets/src/dashboard/components/gridComponents/new/NewColumn.jsx 
b/superset/assets/src/dashboard/components/gridComponents/new/NewColumn.jsx
index 4698736fbe..1c691c7436 100644
--- a/superset/assets/src/dashboard/components/gridComponents/new/NewColumn.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/new/NewColumn.jsx
@@ -1,9 +1,9 @@
 import React from 'react';
+import { t } from '@superset-ui/translation';
 
 import { COLUMN_TYPE } from '../../../util/componentTypes';
 import { NEW_COLUMN_ID } from '../../../util/constants';
 import DraggableNewComponent from './DraggableNewComponent';
-import { t } from '../../../../locales';
 
 export default function DraggableNewColumn() {
   return (
diff --git 
a/superset/assets/src/dashboard/components/gridComponents/new/NewDivider.jsx 
b/superset/assets/src/dashboard/components/gridComponents/new/NewDivider.jsx
index 93518db0a1..939ce6c861 100644
--- a/superset/assets/src/dashboard/components/gridComponents/new/NewDivider.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/new/NewDivider.jsx
@@ -1,9 +1,9 @@
 import React from 'react';
+import { t } from '@superset-ui/translation';
 
 import { DIVIDER_TYPE } from '../../../util/componentTypes';
 import { NEW_DIVIDER_ID } from '../../../util/constants';
 import DraggableNewComponent from './DraggableNewComponent';
-import { t } from '../../../../locales';
 
 export default function DraggableNewDivider() {
   return (
diff --git 
a/superset/assets/src/dashboard/components/gridComponents/new/NewHeader.jsx 
b/superset/assets/src/dashboard/components/gridComponents/new/NewHeader.jsx
index 8acf85330a..78ac9dd693 100644
--- a/superset/assets/src/dashboard/components/gridComponents/new/NewHeader.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/new/NewHeader.jsx
@@ -1,9 +1,9 @@
 import React from 'react';
+import { t } from '@superset-ui/translation';
 
 import { HEADER_TYPE } from '../../../util/componentTypes';
 import { NEW_HEADER_ID } from '../../../util/constants';
 import DraggableNewComponent from './DraggableNewComponent';
-import { t } from '../../../../locales';
 
 export default function DraggableNewHeader() {
   return (
diff --git 
a/superset/assets/src/dashboard/components/gridComponents/new/NewRow.jsx 
b/superset/assets/src/dashboard/components/gridComponents/new/NewRow.jsx
index 7a2ae01a6b..68b0ae0ca8 100644
--- a/superset/assets/src/dashboard/components/gridComponents/new/NewRow.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/new/NewRow.jsx
@@ -1,9 +1,9 @@
 import React from 'react';
+import { t } from '@superset-ui/translation';
 
 import { ROW_TYPE } from '../../../util/componentTypes';
 import { NEW_ROW_ID } from '../../../util/constants';
 import DraggableNewComponent from './DraggableNewComponent';
-import { t } from '../../../../locales';
 
 export default function DraggableNewRow() {
   return (
diff --git 
a/superset/assets/src/dashboard/components/gridComponents/new/NewTabs.jsx 
b/superset/assets/src/dashboard/components/gridComponents/new/NewTabs.jsx
index a77b65e39f..9127c94d4a 100644
--- a/superset/assets/src/dashboard/components/gridComponents/new/NewTabs.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/new/NewTabs.jsx
@@ -1,9 +1,9 @@
 import React from 'react';
+import { t } from '@superset-ui/translation';
 
 import { TABS_TYPE } from '../../../util/componentTypes';
 import { NEW_TABS_ID } from '../../../util/constants';
 import DraggableNewComponent from './DraggableNewComponent';
-import { t } from '../../../../locales';
 
 export default function DraggableNewTabs() {
   return (
diff --git 
a/superset/assets/src/dashboard/components/menu/MarkdownModeDropdown.jsx 
b/superset/assets/src/dashboard/components/menu/MarkdownModeDropdown.jsx
index 10aa932233..0be5e6f18d 100644
--- a/superset/assets/src/dashboard/components/menu/MarkdownModeDropdown.jsx
+++ b/superset/assets/src/dashboard/components/menu/MarkdownModeDropdown.jsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { t } from '../../../locales';
+import { t } from '@superset-ui/translation';
 
 import PopoverDropdown from './PopoverDropdown';
 
diff --git a/superset/assets/src/dashboard/reducers/sliceEntities.js 
b/superset/assets/src/dashboard/reducers/sliceEntities.js
index a96368a9ac..9f790bec58 100644
--- a/superset/assets/src/dashboard/reducers/sliceEntities.js
+++ b/superset/assets/src/dashboard/reducers/sliceEntities.js
@@ -1,11 +1,11 @@
+import { t } from '@superset-ui/translation';
+
 import {
   FETCH_ALL_SLICES_FAILED,
   FETCH_ALL_SLICES_STARTED,
   SET_ALL_SLICES,
 } from '../actions/sliceEntities';
 
-import { t } from '../../locales';
-
 export const initSliceEntities = {
   slices: {},
   isLoading: true,
diff --git a/superset/assets/src/dashboard/util/backgroundStyleOptions.js 
b/superset/assets/src/dashboard/util/backgroundStyleOptions.js
index 926e7f1407..885801fb91 100644
--- a/superset/assets/src/dashboard/util/backgroundStyleOptions.js
+++ b/superset/assets/src/dashboard/util/backgroundStyleOptions.js
@@ -1,4 +1,4 @@
-import { t } from '../../locales';
+import { t } from '@superset-ui/translation';
 import { BACKGROUND_TRANSPARENT, BACKGROUND_WHITE } from './constants';
 
 export default [
diff --git a/superset/assets/src/dashboard/util/headerStyleOptions.js 
b/superset/assets/src/dashboard/util/headerStyleOptions.js
index a37bd5fdfc..815b032b7e 100644
--- a/superset/assets/src/dashboard/util/headerStyleOptions.js
+++ b/superset/assets/src/dashboard/util/headerStyleOptions.js
@@ -1,4 +1,4 @@
-import { t } from '../../locales';
+import { t } from '@superset-ui/translation';
 import { SMALL_HEADER, MEDIUM_HEADER, LARGE_HEADER } from './constants';
 
 export default [
diff --git a/superset/assets/src/datasource/DatasourceEditor.jsx 
b/superset/assets/src/datasource/DatasourceEditor.jsx
index 7c7c8d212a..98d0c9d76a 100644
--- a/superset/assets/src/datasource/DatasourceEditor.jsx
+++ b/superset/assets/src/datasource/DatasourceEditor.jsx
@@ -2,10 +2,9 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Alert, Badge, Col, Label, Tabs, Tab, Well } from 'react-bootstrap';
 import shortid from 'shortid';
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 
-import { t } from '../locales';
-
 import Button from '../components/Button';
 import Loading from '../components/Loading';
 import CheckboxControl from '../explore/components/controls/CheckboxControl';
diff --git a/superset/assets/src/datasource/DatasourceModal.jsx 
b/superset/assets/src/datasource/DatasourceModal.jsx
index 0d4d8e217d..0b232d7d6c 100644
--- a/superset/assets/src/datasource/DatasourceModal.jsx
+++ b/superset/assets/src/datasource/DatasourceModal.jsx
@@ -2,9 +2,9 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Alert, Button, Modal } from 'react-bootstrap';
 import Dialog from 'react-bootstrap-dialog';
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 
-import { t } from '../locales';
 import DatasourceEditor from '../datasource/DatasourceEditor';
 import withToasts from '../messageToasts/enhancers/withToasts';
 
diff --git a/superset/assets/src/explore/App.jsx 
b/superset/assets/src/explore/App.jsx
index 277d34c637..e14363ea0d 100644
--- a/superset/assets/src/explore/App.jsx
+++ b/superset/assets/src/explore/App.jsx
@@ -11,11 +11,13 @@ import ExploreViewContainer from 
'./components/ExploreViewContainer';
 import getInitialState from './reducers/getInitialState';
 import rootReducer from './reducers/index';
 
-import { appSetup } from '../common';
+import setupApp from '../setup/setupApp';
+import setupPlugins from '../setup/setupPlugins';
 import './main.css';
 import '../../stylesheets/reactable-pagination.css';
 
-appSetup();
+setupApp();
+setupPlugins();
 
 const exploreViewContainer = document.getElementById('app');
 const bootstrapData = 
JSON.parse(exploreViewContainer.getAttribute('data-bootstrap'));
diff --git a/superset/assets/src/explore/actions/exploreActions.js 
b/superset/assets/src/explore/actions/exploreActions.js
index 2323bf2bb3..df792b22cd 100644
--- a/superset/assets/src/explore/actions/exploreActions.js
+++ b/superset/assets/src/explore/actions/exploreActions.js
@@ -1,7 +1,7 @@
 /* eslint camelcase: 0 */
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 import { addDangerToast } from '../../messageToasts/actions';
-import { t } from '../../locales';
 
 const FAVESTAR_BASE_URL = '/superset/favstar/slice';
 
diff --git 
a/superset/assets/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx
 
b/superset/assets/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx
index 84f8fadc2e..5ddbe4a9c6 100644
--- 
a/superset/assets/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx
+++ 
b/superset/assets/src/explore/components/AdhocFilterEditPopoverSimpleTabContent.jsx
@@ -2,12 +2,12 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { FormGroup } from 'react-bootstrap';
 import VirtualizedSelect from 'react-virtualized-select';
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 
 import AdhocFilter, { EXPRESSION_TYPES, CLAUSES } from '../AdhocFilter';
 import adhocMetricType from '../propTypes/adhocMetricType';
 import columnType from '../propTypes/columnType';
-import { t } from '../../locales';
 import {
   OPERATORS,
   TABLE_ONLY_OPERATORS,
diff --git 
a/superset/assets/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx
 
b/superset/assets/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx
index 6e5fe3c561..e9c31d9195 100644
--- 
a/superset/assets/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx
+++ 
b/superset/assets/src/explore/components/AdhocFilterEditPopoverSqlTabContent.jsx
@@ -7,6 +7,7 @@ import 'brace/theme/github';
 import 'brace/ext/language_tools';
 import { FormGroup } from 'react-bootstrap';
 import VirtualizedSelect from 'react-virtualized-select';
+import { t } from '@superset-ui/translation';
 
 import { sqlWords } from '../../SqlLab/components/AceEditorWrapper';
 import AdhocFilter, { EXPRESSION_TYPES, CLAUSES } from '../AdhocFilter';
@@ -14,7 +15,6 @@ import adhocMetricType from '../propTypes/adhocMetricType';
 import columnType from '../propTypes/columnType';
 import OnPasteSelect from '../../components/OnPasteSelect';
 import VirtualizedRendererWrap from '../../components/VirtualizedRendererWrap';
-import { t } from '../../locales';
 
 const propTypes = {
   adhocFilter: PropTypes.instanceOf(AdhocFilter).isRequired,
diff --git a/superset/assets/src/explore/components/AdhocMetricEditPopover.jsx 
b/superset/assets/src/explore/components/AdhocMetricEditPopover.jsx
index 96b0314b45..73609a024c 100644
--- a/superset/assets/src/explore/components/AdhocMetricEditPopover.jsx
+++ b/superset/assets/src/explore/components/AdhocMetricEditPopover.jsx
@@ -7,9 +7,9 @@ import AceEditor from 'react-ace';
 import 'brace/mode/sql';
 import 'brace/theme/github';
 import 'brace/ext/language_tools';
+import { t } from '@superset-ui/translation';
 
 import { AGGREGATES } from '../constants';
-import { t } from '../../locales';
 import VirtualizedRendererWrap from '../../components/VirtualizedRendererWrap';
 import OnPasteSelect from '../../components/OnPasteSelect';
 import AdhocMetricEditPopoverTitle from './AdhocMetricEditPopoverTitle';
diff --git a/superset/assets/src/explore/components/ControlHeader.jsx 
b/superset/assets/src/explore/components/ControlHeader.jsx
index 9dcdc61889..eca86c9325 100644
--- a/superset/assets/src/explore/components/ControlHeader.jsx
+++ b/superset/assets/src/explore/components/ControlHeader.jsx
@@ -1,8 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import { t } from '@superset-ui/translation';
 import { ControlLabel, OverlayTrigger, Tooltip } from 'react-bootstrap';
 import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
-import { t } from '../../locales';
 
 const propTypes = {
   name: PropTypes.string,
diff --git a/superset/assets/src/explore/components/ControlPanelsContainer.jsx 
b/superset/assets/src/explore/components/ControlPanelsContainer.jsx
index fba2e5761a..7730a02eb9 100644
--- a/superset/assets/src/explore/components/ControlPanelsContainer.jsx
+++ b/superset/assets/src/explore/components/ControlPanelsContainer.jsx
@@ -4,13 +4,14 @@ import PropTypes from 'prop-types';
 import { bindActionCreators } from 'redux';
 import { connect } from 'react-redux';
 import { Alert, Tab, Tabs } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
+
 import visTypes, { sectionsToRender } from '../visTypes';
 import ControlPanelSection from './ControlPanelSection';
 import ControlRow from './ControlRow';
 import Control from './Control';
 import controls from '../controls';
 import * as actions from '../actions/exploreActions';
-import { t } from '../../locales';
 
 const propTypes = {
   actions: PropTypes.object.isRequired,
diff --git a/superset/assets/src/explore/components/DisplayQueryButton.jsx 
b/superset/assets/src/explore/components/DisplayQueryButton.jsx
index 0029eff48e..de18adc63a 100644
--- a/superset/assets/src/explore/components/DisplayQueryButton.jsx
+++ b/superset/assets/src/explore/components/DisplayQueryButton.jsx
@@ -8,6 +8,7 @@ import jsonSyntax from 
'react-syntax-highlighter/languages/hljs/json';
 import github from 'react-syntax-highlighter/styles/hljs/github';
 import { DropdownButton, MenuItem, Row, Col, FormControl } from 
'react-bootstrap';
 import { Table } from 'reactable';
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
 
 import CopyToClipboard from './../../components/CopyToClipboard';
@@ -16,7 +17,6 @@ import { getExploreUrlAndPayload } from '../exploreUtils';
 import Loading from '../../components/Loading';
 import ModalTrigger from './../../components/ModalTrigger';
 import Button from '../../components/Button';
-import { t } from '../../locales';
 import RowCountLabel from './RowCountLabel';
 
 registerLanguage('markdown', markdownSyntax);
diff --git a/superset/assets/src/explore/components/EmbedCodeButton.jsx 
b/superset/assets/src/explore/components/EmbedCodeButton.jsx
index e6cccacce5..ff28fbbfeb 100644
--- a/superset/assets/src/explore/components/EmbedCodeButton.jsx
+++ b/superset/assets/src/explore/components/EmbedCodeButton.jsx
@@ -1,9 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Popover, OverlayTrigger } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
+
 import CopyToClipboard from './../../components/CopyToClipboard';
 import { getExploreLongUrl } from '../exploreUtils';
-import { t } from '../../locales';
 
 const propTypes = {
   latestQueryFormData: PropTypes.object.isRequired,
diff --git a/superset/assets/src/explore/components/ExploreActionButtons.jsx 
b/superset/assets/src/explore/components/ExploreActionButtons.jsx
index 1e604240e1..6bb059c66b 100644
--- a/superset/assets/src/explore/components/ExploreActionButtons.jsx
+++ b/superset/assets/src/explore/components/ExploreActionButtons.jsx
@@ -1,10 +1,11 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
+import { t } from '@superset-ui/translation';
+
 import URLShortLinkButton from '../../components/URLShortLinkButton';
 import EmbedCodeButton from './EmbedCodeButton';
 import DisplayQueryButton from './DisplayQueryButton';
-import { t } from '../../locales';
 import { exportChart, getExploreLongUrl } from '../exploreUtils';
 
 const propTypes = {
diff --git a/superset/assets/src/explore/components/ExploreChartHeader.jsx 
b/superset/assets/src/explore/components/ExploreChartHeader.jsx
index 8c9ea91f2a..3d6eb84f24 100644
--- a/superset/assets/src/explore/components/ExploreChartHeader.jsx
+++ b/superset/assets/src/explore/components/ExploreChartHeader.jsx
@@ -1,5 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import { t } from '@superset-ui/translation';
 
 import { chartPropShape } from '../../dashboard/util/propShapes';
 import ExploreActionButtons from './ExploreActionButtons';
@@ -10,7 +11,6 @@ import FaveStar from '../../components/FaveStar';
 import TooltipWrapper from '../../components/TooltipWrapper';
 import Timer from '../../components/Timer';
 import CachedLabel from '../../components/CachedLabel';
-import { t } from '../../locales';
 
 const CHART_STATUS_MAP = {
   failed: 'danger',
diff --git a/superset/assets/src/explore/components/RowCountLabel.jsx 
b/superset/assets/src/explore/components/RowCountLabel.jsx
index 3367d1391f..1ba21020fc 100644
--- a/superset/assets/src/explore/components/RowCountLabel.jsx
+++ b/superset/assets/src/explore/components/RowCountLabel.jsx
@@ -1,8 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Label } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 
-import { t } from '../../locales';
 import { defaultNumberFormatter } from '../../modules/utils';
 import TooltipWrapper from '../../components/TooltipWrapper';
 
diff --git a/superset/assets/src/explore/components/SaveModal.jsx 
b/superset/assets/src/explore/components/SaveModal.jsx
index 5a16659132..930b6762fb 100644
--- a/superset/assets/src/explore/components/SaveModal.jsx
+++ b/superset/assets/src/explore/components/SaveModal.jsx
@@ -2,10 +2,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { connect } from 'react-redux';
-
 import { Modal, Alert, Button, Radio } from 'react-bootstrap';
 import Select from 'react-select';
-import { t } from '../../locales';
+import { t } from '@superset-ui/translation';
+
 import { supersetURL } from '../../utils/common';
 import { EXPLORE_ONLY_VIZ_TYPE } from '../constants';
 
diff --git 
a/superset/assets/src/explore/components/controls/AdhocFilterControl.jsx 
b/superset/assets/src/explore/components/controls/AdhocFilterControl.jsx
index b51704d4b6..95983e5076 100644
--- a/superset/assets/src/explore/components/controls/AdhocFilterControl.jsx
+++ b/superset/assets/src/explore/components/controls/AdhocFilterControl.jsx
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import VirtualizedSelect from 'react-virtualized-select';
 
-import { t } from '../../../locales';
+import { t } from '@superset-ui/translation';
 import ControlHeader from '../ControlHeader';
 import adhocFilterType from '../../propTypes/adhocFilterType';
 import adhocMetricType from '../../propTypes/adhocMetricType';
diff --git 
a/superset/assets/src/explore/components/controls/AnnotationLayer.jsx 
b/superset/assets/src/explore/components/controls/AnnotationLayer.jsx
index e1c4dcf3bc..c2e6c27550 100644
--- a/superset/assets/src/explore/components/controls/AnnotationLayer.jsx
+++ b/superset/assets/src/explore/components/controls/AnnotationLayer.jsx
@@ -3,8 +3,9 @@ import PropTypes from 'prop-types';
 import { CompactPicker } from 'react-color';
 import { Button } from 'react-bootstrap';
 import mathjs from 'mathjs';
-
+import { t } from '@superset-ui/translation';
 import { SupersetClient } from '@superset-ui/core';
+
 import SelectControl from './SelectControl';
 import TextControl from './TextControl';
 import CheckboxControl from './CheckboxControl';
@@ -21,8 +22,6 @@ import PopoverSection from 
'../../../components/PopoverSection';
 import ControlHeader from '../ControlHeader';
 import { nonEmpty } from '../../validators';
 import getChartMetadataRegistry from 
'../../../visualizations/core/registries/ChartMetadataRegistrySingleton';
-
-import { t } from '../../../locales';
 import getCategoricalSchemeRegistry from 
'../../../modules/colors/CategoricalSchemeRegistrySingleton';
 
 const AUTOMATIC_COLOR = '';
diff --git 
a/superset/assets/src/explore/components/controls/AnnotationLayerControl.jsx 
b/superset/assets/src/explore/components/controls/AnnotationLayerControl.jsx
index b33dab0195..16eb4e6528 100644
--- a/superset/assets/src/explore/components/controls/AnnotationLayerControl.jsx
+++ b/superset/assets/src/explore/components/controls/AnnotationLayerControl.jsx
@@ -2,14 +2,12 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { OverlayTrigger, Popover, ListGroup, ListGroupItem } from 
'react-bootstrap';
 import { connect } from 'react-redux';
+import { t } from '@superset-ui/translation';
 import { getChartKey } from '../../exploreUtils';
 import { runAnnotationQuery } from '../../../chart/chartAction';
 import InfoTooltipWithTrigger from 
'../../../components/InfoTooltipWithTrigger';
 
-
 import AnnotationLayer from './AnnotationLayer';
-import { t } from '../../../locales';
-
 
 const propTypes = {
   colorScheme: PropTypes.string.isRequired,
diff --git a/superset/assets/src/explore/components/controls/BoundsControl.jsx 
b/superset/assets/src/explore/components/controls/BoundsControl.jsx
index 803a539619..d19b11b34d 100644
--- a/superset/assets/src/explore/components/controls/BoundsControl.jsx
+++ b/superset/assets/src/explore/components/controls/BoundsControl.jsx
@@ -1,8 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Col, Row, FormGroup, FormControl } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 import ControlHeader from '../ControlHeader';
-import { t } from '../../../locales';
 
 const propTypes = {
   onChange: PropTypes.func,
diff --git 
a/superset/assets/src/explore/components/controls/DatasourceControl.jsx 
b/superset/assets/src/explore/components/controls/DatasourceControl.jsx
index cd5ad6b424..271eda9922 100644
--- a/superset/assets/src/explore/components/controls/DatasourceControl.jsx
+++ b/superset/assets/src/explore/components/controls/DatasourceControl.jsx
@@ -9,9 +9,9 @@ import {
   Tooltip,
   Well,
 } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 
 import ControlHeader from '../ControlHeader';
-import { t } from '../../../locales';
 import DatasourceModal from '../../../datasource/DatasourceModal';
 import ColumnOption from '../../../components/ColumnOption';
 import MetricOption from '../../../components/MetricOption';
diff --git 
a/superset/assets/src/explore/components/controls/DateFilterControl.jsx 
b/superset/assets/src/explore/components/controls/DateFilterControl.jsx
index 2c19b830b7..ef5f497b77 100644
--- a/superset/assets/src/explore/components/controls/DateFilterControl.jsx
+++ b/superset/assets/src/explore/components/controls/DateFilterControl.jsx
@@ -18,10 +18,10 @@ import {
 import Datetime from 'react-datetime';
 import 'react-datetime/css/react-datetime.css';
 import moment from 'moment';
+import { t } from '@superset-ui/translation';
 
 import './DateFilterControl.css';
 import ControlHeader from '../ControlHeader';
-import { t } from '../../../locales';
 import PopoverSection from '../../../components/PopoverSection';
 
 const TYPES = Object.freeze({
diff --git a/superset/assets/src/explore/components/controls/MetricsControl.jsx 
b/superset/assets/src/explore/components/controls/MetricsControl.jsx
index 40f73da820..7bbb4d111b 100644
--- a/superset/assets/src/explore/components/controls/MetricsControl.jsx
+++ b/superset/assets/src/explore/components/controls/MetricsControl.jsx
@@ -1,8 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import VirtualizedSelect from 'react-virtualized-select';
+import { t } from '@superset-ui/translation';
+
 import ControlHeader from '../ControlHeader';
-import { t } from '../../../locales';
 import VirtualizedRendererWrap from 
'../../../components/VirtualizedRendererWrap';
 import OnPasteSelect from '../../../components/OnPasteSelect';
 import MetricDefinitionOption from '../MetricDefinitionOption';
diff --git 
a/superset/assets/src/explore/components/controls/SelectAsyncControl.jsx 
b/superset/assets/src/explore/components/controls/SelectAsyncControl.jsx
index 2fb778bd4a..b004369be8 100644
--- a/superset/assets/src/explore/components/controls/SelectAsyncControl.jsx
+++ b/superset/assets/src/explore/components/controls/SelectAsyncControl.jsx
@@ -1,9 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import { t } from '@superset-ui/translation';
+
 import Select from '../../../components/AsyncSelect';
 import ControlHeader from '../ControlHeader';
-import { t } from '../../../locales';
-
 import withToasts from '../../../messageToasts/enhancers/withToasts';
 
 const propTypes = {
diff --git a/superset/assets/src/explore/components/controls/SelectControl.jsx 
b/superset/assets/src/explore/components/controls/SelectControl.jsx
index 4f1e8da469..9e4dc29c1e 100644
--- a/superset/assets/src/explore/components/controls/SelectControl.jsx
+++ b/superset/assets/src/explore/components/controls/SelectControl.jsx
@@ -2,8 +2,9 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import VirtualizedSelect from 'react-virtualized-select';
 import Select, { Creatable } from 'react-select';
+import { t } from '@superset-ui/translation';
+
 import ControlHeader from '../ControlHeader';
-import { t } from '../../../locales';
 import VirtualizedRendererWrap from 
'../../../components/VirtualizedRendererWrap';
 import OnPasteSelect from '../../../components/OnPasteSelect';
 
diff --git a/superset/assets/src/explore/components/controls/SpatialControl.jsx 
b/superset/assets/src/explore/components/controls/SpatialControl.jsx
index dac5cb9ca8..5b9f8ac3fd 100644
--- a/superset/assets/src/explore/components/controls/SpatialControl.jsx
+++ b/superset/assets/src/explore/components/controls/SpatialControl.jsx
@@ -3,12 +3,12 @@ import PropTypes from 'prop-types';
 import {
   Row, Col, Button, Label, OverlayTrigger, Popover,
 } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 
 import ControlHeader from '../ControlHeader';
 import SelectControl from './SelectControl';
 import PopoverSection from '../../../components/PopoverSection';
 import Checkbox from '../../../components/Checkbox';
-import { t } from '../../../locales';
 
 const spatialTypes = {
   latlong: 'latlong',
diff --git 
a/superset/assets/src/explore/components/controls/TextAreaControl.jsx 
b/superset/assets/src/explore/components/controls/TextAreaControl.jsx
index 4a500ea307..c533804c0d 100644
--- a/superset/assets/src/explore/components/controls/TextAreaControl.jsx
+++ b/superset/assets/src/explore/components/controls/TextAreaControl.jsx
@@ -8,12 +8,12 @@ import 'brace/mode/json';
 import 'brace/mode/html';
 import 'brace/mode/markdown';
 import 'brace/mode/javascript';
-
 import 'brace/theme/textmate';
 
+import { t } from '@superset-ui/translation';
+
 import ControlHeader from '../ControlHeader';
 import ModalTrigger from '../../../components/ModalTrigger';
-import { t } from '../../../locales';
 
 const propTypes = {
   name: PropTypes.string,
diff --git a/superset/assets/src/explore/components/controls/VizTypeControl.jsx 
b/superset/assets/src/explore/components/controls/VizTypeControl.jsx
index 3fb871e513..113d3c55c5 100644
--- a/superset/assets/src/explore/components/controls/VizTypeControl.jsx
+++ b/superset/assets/src/explore/components/controls/VizTypeControl.jsx
@@ -3,9 +3,10 @@ import PropTypes from 'prop-types';
 import {
   Label, Row, Col, FormControl, Modal, OverlayTrigger,
   Tooltip } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
+
 import visTypes from '../../visTypes';
 import ControlHeader from '../ControlHeader';
-import { t } from '../../../locales';
 
 const propTypes = {
   description: PropTypes.string,
diff --git a/superset/assets/src/explore/controls.jsx 
b/superset/assets/src/explore/controls.jsx
index ad32c52dd3..92dfb1c24a 100644
--- a/superset/assets/src/explore/controls.jsx
+++ b/superset/assets/src/explore/controls.jsx
@@ -39,6 +39,7 @@
  * each and every visualization type.
  */
 import React from 'react';
+import { t } from '@superset-ui/translation';
 import {
   formatSelectOptionsForRange,
   formatSelectOptions,
@@ -49,7 +50,6 @@ import { PRIMARY_COLOR } from '../modules/colors';
 import { defaultViewport } from '../modules/geo';
 import ColumnOption from '../components/ColumnOption';
 import OptionDescription from '../components/OptionDescription';
-import { t } from '../locales';
 import getCategoricalSchemeRegistry from 
'../modules/colors/CategoricalSchemeRegistrySingleton';
 import getSequentialSchemeRegistry from 
'../modules/colors/SequentialSchemeRegistrySingleton';
 
diff --git a/superset/assets/src/explore/validators.js 
b/superset/assets/src/explore/validators.js
index b1625d476f..ebf4271ae4 100644
--- a/superset/assets/src/explore/validators.js
+++ b/superset/assets/src/explore/validators.js
@@ -4,7 +4,7 @@
  * as arguments and return something that evals to false if v is valid,
  * and an error message if not valid.
  * */
-import { t } from '../locales';
+import { t } from '@superset-ui/translation';
 
 export function numeric(v) {
   if (v && isNaN(v)) {
diff --git a/superset/assets/src/explore/visTypes.jsx 
b/superset/assets/src/explore/visTypes.jsx
index 30aba88389..9a0943c2fe 100644
--- a/superset/assets/src/explore/visTypes.jsx
+++ b/superset/assets/src/explore/visTypes.jsx
@@ -3,10 +3,11 @@
  * and associated with each and every visualization type.
  */
 import React from 'react';
+import { t } from '@superset-ui/translation';
+
 import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
 import { D3_TIME_FORMAT_OPTIONS } from './controls';
 import * as v from './validators';
-import { t } from '../locales';
 
 export const sections = {
   druidTimeSeries: {
diff --git a/superset/assets/src/i18n.jsx b/superset/assets/src/i18n.jsx
deleted file mode 100644
index b27f7392f2..0000000000
--- a/superset/assets/src/i18n.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import Jed from 'jed';
-
-const DEFAULT_LANGUAGE_PACK = {
-  domain: 'superset',
-  locale_data: {
-    superset: {
-      '': {
-        domain: 'superset',
-        lang: 'en',
-        plural_forms: 'nplurals=1; plural=0',
-      },
-    },
-  },
-};
-
-const i18n = (function () {
-  let languagePack = DEFAULT_LANGUAGE_PACK;
-
-  if (typeof window !== 'undefined') {
-    const root = document.getElementById('app');
-    const bootstrapData = root ? 
JSON.parse(root.getAttribute('data-bootstrap')) : {};
-    if (bootstrapData.common && bootstrapData.common.language_pack) {
-      languagePack = bootstrapData.common.language_pack;
-      delete bootstrapData.common.locale;
-      delete bootstrapData.common.language_pack;
-    }
-  }
-
-  return new Jed(languagePack);
-}());
-
-export default i18n;
diff --git a/superset/assets/src/locales.jsx b/superset/assets/src/locales.jsx
deleted file mode 100644
index 12b1a87646..0000000000
--- a/superset/assets/src/locales.jsx
+++ /dev/null
@@ -1,148 +0,0 @@
-/* eslint-disable global-require, import/no-dynamic-require */
-import React from 'react';
-import { sprintf } from 'sprintf-js';
-import i18n from './i18n';
-
-function formatForReact(formatString, args) {
-  const rv = [];
-  let cursor = 0;
-  sprintf.parse(formatString).forEach((match, idx) => {
-    const cpoyMatch = match;
-    let copyIdx = idx;
-    if (typeof match === 'string') {
-      rv.push(match);
-    } else {
-      let arg = null;
-      if (match[2]) {
-        arg = args[0][match[2][0]];
-      } else if (match[1]) {
-        arg = args[parseInt(match[1], 10) - 1];
-      } else {
-        arg = args[cursor++];
-      }
-      if (React.isValidElement(arg)) {
-        rv.push(React.cloneElement(arg, { key: idx }));
-      } else {
-        cpoyMatch[2] = null;
-        cpoyMatch[1] = 1;
-        rv.push(<span key={copyIdx++}>
-          {sprintf.format([cpoyMatch], [null, arg])}
-        </span>);
-      }
-    }
-  });
-  return rv;
-}
-
-function argsInvolveReact(args) {
-  if (args.some(React.isValidElement)) {
-    return true;
-  }
-  if (args.length === 1 && typeof args[0] === 'object') {
-    return Object.keys(args[0]).some(function (key) {
-      return React.isValidElement(args[0][key]);
-    });
-  }
-  return false;
-}
-
-export function parseComponentTemplate(string) {
-  const rv = {};
-  function process(startPos, group, inGroup) {
-    const regex = /\[(.*?)(:|\])|\]/g;
-    let match;
-    const buf = [];
-    let satisfied = false;
-    let pos = regex.lastIndex = startPos;
-    match = regex.exec(string);
-    while (match !== null) {
-      const substr = string.substr(pos, match.index - pos);
-      if (substr !== '') {
-        buf.push(substr);
-      }
-      if (match[0] === ']') {
-        if (inGroup) {
-          satisfied = true;
-          break;
-        } else {
-          pos = regex.lastIndex;
-          continue;
-        }
-      }
-      if (match[2] === ']') {
-        pos = regex.lastIndex;
-      } else {
-        pos = regex.lastIndex = process(regex.lastIndex, match[1], true);
-      }
-      buf.push({ group: match[1] });
-      match = regex.exec(string);
-    }
-    let endPos = regex.lastIndex;
-    if (!satisfied) {
-      const rest = string.substr(pos);
-      if (rest) {
-        buf.push(rest);
-      }
-      endPos = string.length;
-    }
-    rv[group] = buf;
-    return endPos;
-  }
-  process(0, 'root', false);
-  return rv;
-}
-
-export function renderComponentTemplate(template, components) {
-  let idx = 0;
-  function renderGroup(group) {
-    const children = [];
-    (template[group] || []).forEach((item) => {
-      if (typeof item === 'string') {
-        children.push(<span key={idx++}>{item}</span>);
-      } else {
-        children.push(renderGroup(item.group));
-      }
-    });
-    let reference = components[group] || <span key={idx++} />;
-    if (!React.isValidElement(reference)) {
-      reference = <span key={idx++}>{reference}</span>;
-    }
-    if (children.length > 0) {
-      return React.cloneElement(reference, { key: idx++ }, children);
-    }
-    return React.cloneElement(reference, { key: idx++ });
-  }
-  return renderGroup('root');
-}
-
-export function format(formatString, args) {
-  if (argsInvolveReact(args)) {
-    return formatForReact(formatString, args);
-  }
-  return sprintf(formatString, ...args);
-}
-
-export function gettext(string, ...args) {
-  if (!string || !i18n) {
-    return string;
-  }
-
-  let rv = i18n.gettext(string);
-  if (args.length > 0) {
-    rv = format(rv, args);
-  }
-  return rv;
-}
-
-export function ngettext(singular, plural, ...args) {
-  return format(i18n.ngettext(singular, plural, args[0] || 0), args);
-}
-
-export function gettextComponentTemplate(template, components) {
-  const tmpl = parseComponentTemplate(i18n.gettext(template));
-  return renderComponentTemplate(tmpl, components);
-}
-
-export const t = gettext;
-export const tn = ngettext;
-export const tct = gettextComponentTemplate;
diff --git a/superset/assets/src/modules/utils.js 
b/superset/assets/src/modules/utils.js
index 1bb656fc90..bc22c5a06b 100644
--- a/superset/assets/src/modules/utils.js
+++ b/superset/assets/src/modules/utils.js
@@ -1,10 +1,7 @@
 /* eslint camelcase: 0 */
-import d3 from 'd3';
 import $ from 'jquery';
-import { SupersetClient } from '@superset-ui/core';
+import d3 from 'd3';
 import { formatDate, UTC } from './dates';
-import { COMMON_ERR_MESSAGES } from '../utils/common';
-import { t } from '../locales';
 
 const siFormatter = d3.format('.3s');
 
@@ -109,70 +106,6 @@ export function showModal(options) {
   $(options.modalSelector).modal('show');
 }
 
-
-function showApiMessage(resp) {
-  const template =
-    '<div class="alert"> ' +
-    '<button type="button" class="close" ' +
-    'data-dismiss="alert">\xD7</button> </div>';
-  const severity = resp.severity || 'info';
-  $(template).addClass('alert-' + severity)
-             .append(resp.message)
-             .appendTo($('#alert-container'));
-}
-
-export function getClientErrorObject(response) {
-  // takes a Response object as input, attempts to read response as Json if 
possible,
-  // and returns a Promise that resolves to a plain object with error key and 
text value.
-  return new Promise((resolve) => {
-    if (typeof response === 'string') {
-      resolve({ error: response });
-    } else if (response && response.constructor === Response && 
!response.bodyUsed) {
-      // attempt to read the body as json, and fallback to text. we must clone 
the
-      // response in order to fallback to .text() because Response is 
single-read
-      response.clone().json().then((errorJson) => {
-        let error = { ...response, ...errorJson };
-        if (error.stack) {
-          error = {
-            ...error,
-            error: t('Unexpected error: ') +
-              (error.description || t('(no description, click to see stack 
trace)')),
-            stacktrace: error.stack,
-          };
-        } else if (error.responseText && error.responseText.indexOf('CSRF') >= 
0) {
-          error = {
-            ...error,
-            error: COMMON_ERR_MESSAGES.SESSION_TIMED_OUT,
-          };
-        }
-        resolve(error);
-      }).catch(() => {
-        // fall back to reading as text
-        response.text().then((errorText) => {
-          resolve({ ...response, error: errorText });
-        });
-      });
-    } else {
-      // fall back to Response.statusText or generic error of we cannot read 
the response
-      resolve({ ...response, error: response.statusText || t('An error 
occurred') });
-    }
-  });
-
-}
-
-export function toggleCheckbox(apiUrlPrefix, selector) {
-  SupersetClient.get({ endpoint: apiUrlPrefix + $(selector)[0].checked })
-    .then(() => {})
-    .catch(response =>
-      getClientErrorObject(response)
-        .then((parsedResp) => {
-          if (parsedResp && parsedResp.message) {
-            showApiMessage(parsedResp);
-          }
-        }),
-      );
-}
-
 /**
  * Fix the height of the table body of a DataTable with scrollY set
  */
diff --git a/superset/assets/src/preamble.js b/superset/assets/src/preamble.js
new file mode 100644
index 0000000000..9d80dc9e59
--- /dev/null
+++ b/superset/assets/src/preamble.js
@@ -0,0 +1,24 @@
+import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';
+import { configure } from '@superset-ui/translation';
+import setupClient from './setup/setupClient';
+import setupColors from './setup/setupColors';
+
+// Configure translation
+if (typeof window !== 'undefined') {
+  const root = document.getElementById('app');
+  const bootstrapData = root ? JSON.parse(root.getAttribute('data-bootstrap')) 
: {};
+  if (bootstrapData.common && bootstrapData.common.language_pack) {
+    const languagePack = bootstrapData.common.language_pack;
+    configure({ languagePack });
+  } else {
+    configure();
+  }
+} else {
+  configure();
+}
+
+// Setup SupersetClient
+setupClient();
+
+// Setup color palettes
+setupColors();
diff --git a/superset/assets/src/profile/App.jsx 
b/superset/assets/src/profile/App.jsx
index 3ad5bad015..d4e175b3f3 100644
--- a/superset/assets/src/profile/App.jsx
+++ b/superset/assets/src/profile/App.jsx
@@ -7,11 +7,11 @@ import { Provider } from 'react-redux';
 import App from './components/App';
 import messageToastReducer from '../messageToasts/reducers';
 import { initEnhancer } from '../reduxUtils';
-import { appSetup } from '../common';
+import setupApp from '../setup/setupApp';
 
 import './main.css';
 
-appSetup();
+setupApp();
 
 const profileViewContainer = document.getElementById('app');
 const bootstrap = 
JSON.parse(profileViewContainer.getAttribute('data-bootstrap'));
diff --git a/superset/assets/src/profile/components/App.jsx 
b/superset/assets/src/profile/components/App.jsx
index 0cb9034182..c71f7b2cde 100644
--- a/superset/assets/src/profile/components/App.jsx
+++ b/superset/assets/src/profile/components/App.jsx
@@ -1,12 +1,13 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Col, Row, Tabs, Tab, Panel } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
+
 import Favorites from './Favorites';
 import UserInfo from './UserInfo';
 import Security from './Security';
 import RecentActivity from './RecentActivity';
 import CreatedContent from './CreatedContent';
-import { t } from '../../locales';
 
 const propTypes = {
   user: PropTypes.object.isRequired,
diff --git a/superset/assets/src/profile/components/CreatedContent.jsx 
b/superset/assets/src/profile/components/CreatedContent.jsx
index 00d0f9d1ea..976162fdc7 100644
--- a/superset/assets/src/profile/components/CreatedContent.jsx
+++ b/superset/assets/src/profile/components/CreatedContent.jsx
@@ -1,8 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import moment from 'moment';
+import { t } from '@superset-ui/translation';
+
 import TableLoader from '../../components/TableLoader';
-import { t } from '../../locales';
 
 const propTypes = {
   user: PropTypes.object.isRequired,
diff --git a/superset/assets/src/profile/components/Favorites.jsx 
b/superset/assets/src/profile/components/Favorites.jsx
index e0ef112d45..c6eb3e7a76 100644
--- a/superset/assets/src/profile/components/Favorites.jsx
+++ b/superset/assets/src/profile/components/Favorites.jsx
@@ -1,8 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import moment from 'moment';
+import { t } from '@superset-ui/translation';
+
 import TableLoader from '../../components/TableLoader';
-import { t } from '../../locales';
 
 const propTypes = {
   user: PropTypes.object.isRequired,
diff --git a/superset/assets/src/profile/components/Security.jsx 
b/superset/assets/src/profile/components/Security.jsx
index 748be6b840..47831198dd 100644
--- a/superset/assets/src/profile/components/Security.jsx
+++ b/superset/assets/src/profile/components/Security.jsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Badge, Label } from 'react-bootstrap';
-import { t } from '../../locales';
+import { t } from '@superset-ui/translation';
 
 const propTypes = {
   user: PropTypes.object.isRequired,
diff --git a/superset/assets/src/profile/components/UserInfo.jsx 
b/superset/assets/src/profile/components/UserInfo.jsx
index 4cc54be6ac..9bd61bedab 100644
--- a/superset/assets/src/profile/components/UserInfo.jsx
+++ b/superset/assets/src/profile/components/UserInfo.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import Gravatar from 'react-gravatar';
 import moment from 'moment';
 import { Panel } from 'react-bootstrap';
-import { t } from '../../locales';
+import { t } from '@superset-ui/translation';
 
 const propTypes = {
   user: PropTypes.object.isRequired,
diff --git a/superset/assets/src/setup/setupApp.js 
b/superset/assets/src/setup/setupApp.js
new file mode 100644
index 0000000000..7d2fda2dc8
--- /dev/null
+++ b/superset/assets/src/setup/setupApp.js
@@ -0,0 +1,57 @@
+/* eslint global-require: 0 */
+import $ from 'jquery';
+import { SupersetClient } from '@superset-ui/core';
+import getClientErrorObject from '../utils/getClientErrorObject';
+
+function showApiMessage(resp) {
+  const template =
+    '<div class="alert"> ' +
+    '<button type="button" class="close" ' +
+    'data-dismiss="alert">\xD7</button> </div>';
+  const severity = resp.severity || 'info';
+  $(template).addClass('alert-' + severity)
+    .append(resp.message)
+    .appendTo($('#alert-container'));
+}
+
+function toggleCheckbox(apiUrlPrefix, selector) {
+  SupersetClient.get({ endpoint: apiUrlPrefix + $(selector)[0].checked })
+    .then(() => {})
+    .catch(response =>
+      getClientErrorObject(response)
+        .then((parsedResp) => {
+          if (parsedResp && parsedResp.message) {
+            showApiMessage(parsedResp);
+          }
+        }),
+      );
+}
+
+export default function setupApp() {
+  $(document).ready(function () {
+    $(':checkbox[data-checkbox-api-prefix]').change(function () {
+      const $this = $(this);
+      const prefix = $this.data('checkbox-api-prefix');
+      const id = $this.attr('id');
+      toggleCheckbox(prefix, '#' + id);
+    });
+
+    // for language picker dropdown
+    $('#language-picker a').click(function (ev) {
+      ev.preventDefault();
+      SupersetClient.get({
+        endpoint: ev.currentTarget.getAttribute('href'),
+        parseMethod: null,
+      })
+        .then(() => {
+          location.reload();
+        });
+    });
+  });
+
+  // A set of hacks to allow apps to run within a FAB template
+  // this allows for the server side generated menus to function
+  window.$ = $;
+  window.jQuery = $;
+  require('bootstrap');
+}
diff --git a/superset/assets/src/utils/common.js 
b/superset/assets/src/utils/common.js
index 9fbe35bcb9..a22ca9a1c0 100644
--- a/superset/assets/src/utils/common.js
+++ b/superset/assets/src/utils/common.js
@@ -1,8 +1,5 @@
-/* eslint global-require: 0 */
+import d3 from 'd3';
 import { SupersetClient } from '@superset-ui/core';
-import { t } from '../locales';
-
-const d3 = require('d3');
 
 export const EARTH_CIRCUMFERENCE_KM = 40075.16;
 export const LUMINANCE_RED_WEIGHT = 0.2126;
@@ -113,10 +110,5 @@ export function optionFromValue(opt) {
   return { value: optionValue(opt), label: optionLabel(opt) };
 }
 
-// Error messages used in many places across applications
-export const COMMON_ERR_MESSAGES = {
-  SESSION_TIMED_OUT: t('Your session timed out, please refresh your page and 
try again.'),
-};
-
 // time_range separator
 export const TIME_RANGE_SEPARATOR = ' : ';
diff --git a/superset/assets/src/utils/errorMessages.js 
b/superset/assets/src/utils/errorMessages.js
new file mode 100644
index 0000000000..70117b26eb
--- /dev/null
+++ b/superset/assets/src/utils/errorMessages.js
@@ -0,0 +1,6 @@
+// Error messages used in many places across applications
+const COMMON_ERR_MESSAGES = {
+  SESSION_TIMED_OUT: 'Your session timed out, please refresh your page and try 
again.',
+};
+
+export default COMMON_ERR_MESSAGES;
diff --git a/superset/assets/src/utils/getClientErrorObject.js 
b/superset/assets/src/utils/getClientErrorObject.js
new file mode 100644
index 0000000000..ea0ea443c2
--- /dev/null
+++ b/superset/assets/src/utils/getClientErrorObject.js
@@ -0,0 +1,40 @@
+import { t } from '@superset-ui/translation';
+import COMMON_ERR_MESSAGES from './errorMessages';
+
+export default function getClientErrorObject(response) {
+  // takes a Response object as input, attempts to read response as Json if 
possible,
+  // and returns a Promise that resolves to a plain object with error key and 
text value.
+  return new Promise((resolve) => {
+    if (typeof response === 'string') {
+      resolve({ error: response });
+    } else if (response && response.constructor === Response && 
!response.bodyUsed) {
+      // attempt to read the body as json, and fallback to text. we must clone 
the
+      // response in order to fallback to .text() because Response is 
single-read
+      response.clone().json().then((errorJson) => {
+        let error = { ...response, ...errorJson };
+        if (error.stack) {
+          error = {
+            ...error,
+            error: t('Unexpected error: ') +
+              (error.description || t('(no description, click to see stack 
trace)')),
+            stacktrace: error.stack,
+          };
+        } else if (error.responseText && error.responseText.indexOf('CSRF') >= 
0) {
+          error = {
+            ...error,
+            error: t(COMMON_ERR_MESSAGES.SESSION_TIMED_OUT),
+          };
+        }
+        resolve(error);
+      }).catch(() => {
+        // fall back to reading as text
+        response.text().then((errorText) => {
+          resolve({ ...response, error: errorText });
+        });
+      });
+    } else {
+      // fall back to Response.statusText or generic error of we cannot read 
the response
+      resolve({ ...response, error: response.statusText || t('An error 
occurred') });
+    }
+  });
+}
diff --git a/superset/assets/src/visualizations/EventFlow/EventFlow.jsx 
b/superset/assets/src/visualizations/EventFlow/EventFlow.jsx
index 7359bda95e..4a688e66be 100644
--- a/superset/assets/src/visualizations/EventFlow/EventFlow.jsx
+++ b/superset/assets/src/visualizations/EventFlow/EventFlow.jsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { App, withParentSize } from '@data-ui/event-flow';
-import { t } from '../../locales';
+import { t } from '@superset-ui/translation';
 
 const propTypes = {
   className: PropTypes.string,
diff --git a/superset/assets/src/visualizations/FilterBox/FilterBox.jsx 
b/superset/assets/src/visualizations/FilterBox/FilterBox.jsx
index 907e802025..05b9c9f07f 100644
--- a/superset/assets/src/visualizations/FilterBox/FilterBox.jsx
+++ b/superset/assets/src/visualizations/FilterBox/FilterBox.jsx
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
 import VirtualizedSelect from 'react-virtualized-select';
 import { Creatable } from 'react-select';
 import { Button } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 
 import DateFilterControl from 
'../../explore/components/controls/DateFilterControl';
 import ControlRow from '../../explore/components/ControlRow';
@@ -10,7 +11,6 @@ import Control from '../../explore/components/Control';
 import controls from '../../explore/controls';
 import OnPasteSelect from '../../components/OnPasteSelect';
 import VirtualizedRendererWrap from '../../components/VirtualizedRendererWrap';
-import { t } from '../../locales';
 import './FilterBox.css';
 
 // maps control names to their key in extra_filters
diff --git a/superset/assets/src/visualizations/PlaySlider.jsx 
b/superset/assets/src/visualizations/PlaySlider.jsx
index 88b033524f..891c84840d 100644
--- a/superset/assets/src/visualizations/PlaySlider.jsx
+++ b/superset/assets/src/visualizations/PlaySlider.jsx
@@ -1,14 +1,11 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { Row, Col } from 'react-bootstrap';
-
 import Mousetrap from 'mousetrap';
-
+import { Row, Col } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 import BootrapSliderWrapper from '../components/BootstrapSliderWrapper';
 import './PlaySlider.css';
 
-import { t } from '../locales';
-
 const propTypes = {
   start: PropTypes.number.isRequired,
   step: PropTypes.number.isRequired,
diff --git a/superset/assets/src/visualizations/nvd3/NVD3Vis.js 
b/superset/assets/src/visualizations/nvd3/NVD3Vis.js
index a67c93fb71..1267b422b8 100644
--- a/superset/assets/src/visualizations/nvd3/NVD3Vis.js
+++ b/superset/assets/src/visualizations/nvd3/NVD3Vis.js
@@ -4,9 +4,9 @@ import nv from 'nvd3';
 import mathjs from 'mathjs';
 import moment from 'moment';
 import PropTypes from 'prop-types';
+import { t } from '@superset-ui/translation';
 import 'nvd3/build/nv.d3.min.css';
 
-import { t } from '../../locales';
 import ANNOTATION_TYPES, { applyNativeColumns } from 
'../../modules/AnnotationTypes';
 import { getScale, getColor } from 
'../../modules/colors/CategoricalColorNamespace';
 import { formatDateVerbose } from '../../modules/dates';
diff --git a/superset/assets/src/welcome/App.jsx 
b/superset/assets/src/welcome/App.jsx
index b22c162c73..4a894ca2c7 100644
--- a/superset/assets/src/welcome/App.jsx
+++ b/superset/assets/src/welcome/App.jsx
@@ -6,11 +6,10 @@ import { Provider } from 'react-redux';
 
 import messageToastReducer from '../messageToasts/reducers';
 import { initEnhancer } from '../reduxUtils';
-
-import { appSetup } from '../common';
+import setupApp from '../setup/setupApp';
 import Welcome from './Welcome';
 
-appSetup();
+setupApp();
 
 const container = document.getElementById('app');
 const bootstrap = JSON.parse(container.getAttribute('data-bootstrap'));
diff --git a/superset/assets/src/welcome/DashboardTable.jsx 
b/superset/assets/src/welcome/DashboardTable.jsx
index 29c2988763..c03e34c534 100644
--- a/superset/assets/src/welcome/DashboardTable.jsx
+++ b/superset/assets/src/welcome/DashboardTable.jsx
@@ -2,9 +2,9 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { Table, Tr, Td, unsafe } from 'reactable';
 import { SupersetClient } from '@superset-ui/core';
-import withToasts from '../messageToasts/enhancers/withToasts';
-import { t } from '../locales';
+import { t } from '@superset-ui/translation';
 
+import withToasts from '../messageToasts/enhancers/withToasts';
 import Loading from '../components/Loading';
 import '../../stylesheets/reactable-pagination.css';
 
diff --git a/superset/assets/src/welcome/Welcome.jsx 
b/superset/assets/src/welcome/Welcome.jsx
index 2f4de973ae..d059ee1113 100644
--- a/superset/assets/src/welcome/Welcome.jsx
+++ b/superset/assets/src/welcome/Welcome.jsx
@@ -1,10 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { Panel, Row, Col, Tabs, Tab, FormControl } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
 import RecentActivity from '../profile/components/RecentActivity';
 import Favorites from '../profile/components/Favorites';
 import DashboardTable from './DashboardTable';
-import { t } from '../locales';
 
 const propTypes = {
   user: PropTypes.object.isRequired,
diff --git a/superset/assets/webpack.config.js 
b/superset/assets/webpack.config.js
index 9da65284ce..c37ad55a1e 100644
--- a/superset/assets/webpack.config.js
+++ b/superset/assets/webpack.config.js
@@ -79,19 +79,28 @@ if (isDevMode) {
   output.chunkFilename = '[name].[chunkhash].chunk.js';
 }
 
+const PREAMBLE = [
+  'babel-polyfill',
+  path.join(APP_DIR, '/src/preamble.js'),
+];
+
+function addPreamble(entry) {
+  return PREAMBLE.concat([path.join(APP_DIR, entry)]);
+}
+
 const config = {
   node: {
     fs: 'empty',
   },
   entry: {
-    theme: APP_DIR + '/src/theme.js',
-    common: APP_DIR + '/src/common.js',
-    addSlice: ['babel-polyfill', APP_DIR + '/src/addSlice/index.jsx'],
-    explore: ['babel-polyfill', APP_DIR + '/src/explore/index.jsx'],
-    dashboard: ['babel-polyfill', APP_DIR + '/src/dashboard/index.jsx'],
-    sqllab: ['babel-polyfill', APP_DIR + '/src/SqlLab/index.jsx'],
-    welcome: ['babel-polyfill', APP_DIR + '/src/welcome/index.jsx'],
-    profile: ['babel-polyfill', APP_DIR + '/src/profile/index.jsx'],
+    theme: path.join(APP_DIR, '/src/theme.js'),
+    preamble: PREAMBLE,
+    addSlice: addPreamble('/src/addSlice/index.jsx'),
+    explore: addPreamble('/src/explore/index.jsx'),
+    dashboard: addPreamble('/src/dashboard/index.jsx'),
+    sqllab: addPreamble('/src/SqlLab/index.jsx'),
+    welcome: addPreamble('/src/welcome/index.jsx'),
+    profile: addPreamble('/src/profile/index.jsx'),
   },
   output,
   optimization: {
@@ -103,7 +112,7 @@ const config = {
         default: false,
         major: {
           name: 'vendors-major',
-          test: /[\\/]node_modules\/(brace|react[-]dom|core[-]js)[\\/]/,
+          test: 
/[\\/]node_modules\/(brace|react[-]dom|core[-]js|@superset[-]ui\/translation)[\\/]/,
         },
       },
     },
diff --git a/superset/assets/yarn.lock b/superset/assets/yarn.lock
index a6ffc95c29..a59ef5e26e 100644
--- a/superset/assets/yarn.lock
+++ b/superset/assets/yarn.lock
@@ -406,6 +406,13 @@
     "@babel/runtime" "^7.1.2"
     whatwg-fetch "^2.0.4"
 
+"@superset-ui/translation@^0.2.1":
+  version "0.2.1"
+  resolved 
"https://registry.yarnpkg.com/@superset-ui/translation/-/translation-0.2.1.tgz#252025d566918782bafc25cb9fe2aa31f9917093";
+  dependencies:
+    "@babel/runtime" "^7.1.2"
+    jed "^1.1.1"
+
 "@types/[email protected]":
   version "1.3.3"
   resolved 
"https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a";
@@ -2697,7 +2704,7 @@ class-utils@^0.3.5:
     isobject "^3.0.0"
     static-extend "^0.1.1"
 
-classnames@^2.1.2, classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5:
+classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5:
   version "2.2.6"
   resolved 
"https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce";
 
@@ -4452,10 +4459,6 @@ [email protected]:
   dependencies:
     pify "^2.2.0"
 
-exenv@^1.2.0:
-  version "1.2.2"
-  resolved 
"https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d";
-
 exif-parser@^0.1.9:
   version "0.1.12"
   resolved 
"https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922";
@@ -9350,15 +9353,6 @@ [email protected]:
     bootstrap-slider "9.9.0"
     es6bindall "^0.0.9"
 
-react-bootstrap-table@^4.3.1:
-  version "4.3.1"
-  resolved 
"https://registry.yarnpkg.com/react-bootstrap-table/-/react-bootstrap-table-4.3.1.tgz#f704be55b7f6bf0557d2fc5bec6d25fd307d0cde";
-  dependencies:
-    classnames "^2.1.2"
-    prop-types "^15.5.10"
-    react-modal "^3.1.7"
-    react-s-alert "^1.3.2"
-
 react-bootstrap@^0.31.5:
   version "0.31.5"
   resolved 
"https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-0.31.5.tgz#57040fa8b1274e1e074803c21a1b895fdabea05a";
@@ -9455,7 +9449,7 @@ react-is@^16.3.2, react-is@^16.5.2:
   version "16.5.2"
   resolved 
"https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3";
 
-react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
+react-lifecycles-compat@^3.0.4:
   version "3.0.4"
   resolved 
"https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362";
 
@@ -9488,15 +9482,6 @@ react-markdown@^3.3.0:
     unist-util-visit "^1.3.0"
     xtend "^4.0.1"
 
-react-modal@^3.1.7:
-  version "3.6.1"
-  resolved 
"https://registry.yarnpkg.com/react-modal/-/react-modal-3.6.1.tgz#54d27a1ec2b493bbc451c7efaa3557b6af82332d";
-  dependencies:
-    exenv "^1.2.0"
-    prop-types "^15.5.10"
-    react-lifecycles-compat "^3.0.0"
-    warning "^3.0.0"
-
 react-move@^2.1.0:
   version "2.8.0"
   resolved 
"https://registry.yarnpkg.com/react-move/-/react-move-2.8.0.tgz#63deaf6a0e58ca6769357abfcd91dde44d83a596";
@@ -9536,12 +9521,6 @@ react-resizable@^1.3.3:
     prop-types "15.x"
     react-draggable "^2.2.6 || ^3.0.3"
 
-react-s-alert@^1.3.2:
-  version "1.4.1"
-  resolved 
"https://registry.yarnpkg.com/react-s-alert/-/react-s-alert-1.4.1.tgz#ef3665a9d98c4cf2e448fc2d84e48aeca799bb5a";
-  dependencies:
-    babel-runtime "^6.23.0"
-
 react-search-input@^0.11.3:
   version "0.11.3"
   resolved 
"https://registry.yarnpkg.com/react-search-input/-/react-search-input-0.11.3.tgz#3dd1f9fc584b6bc40a6ee133ae042b6fbb7ae8dd";
@@ -10823,10 +10802,6 @@ split@~0.2.10:
   dependencies:
     through "2"
 
-sprintf-js@^1.1.1:
-  version "1.1.1"
-  resolved 
"https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c";
-
 sprintf-js@~1.0.2:
   version "1.0.3"
   resolved 
"https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c";
diff --git a/superset/templates/superset/base.html 
b/superset/templates/superset/base.html
index b4397f510d..b2dba505dd 100644
--- a/superset/templates/superset/base.html
+++ b/superset/templates/superset/base.html
@@ -10,14 +10,14 @@
 
   {% block head_js %}
     {{super()}}
-    {% for entry in get_unloaded_chunks(js_manifest('theme'), loaded_chunks) %}
-      <script src="{{ entry }}"></script>
-    {% endfor %}
+    {% with filename="theme" %}
+      {% include "superset/partials/_script_tag.html" %}
+    {% endwith %}
   {% endblock %}
 
   {% block tail_js %}
     {{super()}}
-    {% for entry in get_unloaded_chunks(js_manifest('common'), loaded_chunks) 
%}
-      <script src="{{ entry }}"></script>
-    {% endfor %}
+    {% with filename="preamble" %}
+      {% include "superset/partials/_script_tag.html" %}
+    {% endwith %}
   {% endblock %}
diff --git a/superset/templates/superset/basic.html 
b/superset/templates/superset/basic.html
index a5e72f38fa..f096b55ca6 100644
--- a/superset/templates/superset/basic.html
+++ b/superset/templates/superset/basic.html
@@ -29,12 +29,9 @@
 
     {% endblock %}
 
-    {% for entry in get_unloaded_chunks(js_manifest('theme'), loaded_chunks) %}
-      <script src="{{ entry }}"></script>
-    {% endfor %}
-    {% for entry in get_unloaded_chunks(js_manifest('common'), loaded_chunks) 
%}
-      <script src="{{ entry }}"></script>
-    {% endfor %}
+    {% with filename="theme" %}
+      {% include "superset/partials/_script_tag.html" %}
+    {% endwith %}
 
     <input
       type="hidden"
@@ -79,10 +76,9 @@ <h4 class="modal-title"></h4>
     </div>
     {% block tail_js %}
       {% if entry %}
-        {% set entry_files = js_manifest(entry) %}
-        {% for entry in get_unloaded_chunks(entry_files, loaded_chunks) %}
-          <script src="{{ entry }}"></script>
-        {% endfor %}
+        {% with filename=entry %}
+          {% include "superset/partials/_script_tag.html" %}
+        {% endwith %}
       {% endif %}
     {% endblock %}
   </body>
diff --git a/superset/templates/superset/partials/_script_tag.html 
b/superset/templates/superset/partials/_script_tag.html
index f530c7bd40..1677925a85 100644
--- a/superset/templates/superset/partials/_script_tag.html
+++ b/superset/templates/superset/partials/_script_tag.html
@@ -1,4 +1,4 @@
-{% block tail_js %}
+{% block partial_js %}
   {% for entry in get_unloaded_chunks(js_manifest(filename), loaded_chunks) %}
     <script src="{{ entry }}"></script>
   {% endfor %}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to