diff --git a/babel/messages.pot b/babel/messages.pot index 198b467626b7..41851e382ad0 100755 --- a/babel/messages.pot +++ b/babel/messages.pot @@ -916,3 +916,55 @@ msgstr "" msgid "Saved Queries" msgstr "" +#: superset/assets/javascripts/explore/components/SaveModal.jsx:73 +msgid "Please enter a slice name" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:88 +msgid "Please select a dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:96 +msgid "Please enter a dashboard name" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:134 +msgid "Save A Slice" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:155 +msgid "Overwrite slice" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:164 +msgid "Save as" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:168 +msgid "[slice name]" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:181 +msgid "Do not add to a dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:189 +msgid "Add slice to existing dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:203 +msgid "Add to new dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:208 +msgid "[dashboard name]" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:220 +msgid "Save" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:229 +msgid "Save & go to dashboard" +msgstr "" + diff --git a/superset/.config.py.swp b/superset/.config.py.swp new file mode 100644 index 000000000000..23cdfdb4edb5 Binary files /dev/null and b/superset/.config.py.swp differ diff --git a/superset/assets/javascripts/explore/components/SaveModal.jsx b/superset/assets/javascripts/explore/components/SaveModal.jsx index 4dbff36acfea..a6b645e85586 100644 --- a/superset/assets/javascripts/explore/components/SaveModal.jsx +++ b/superset/assets/javascripts/explore/components/SaveModal.jsx @@ -5,6 +5,7 @@ import $ from 'jquery'; import { Modal, Alert, Button, Radio } from 'react-bootstrap'; import Select from 'react-select'; import { connect } from 'react-redux'; +import { t } from '../../locale'; const propTypes = { can_overwrite: PropTypes.bool, @@ -69,7 +70,7 @@ class SaveModal extends React.Component { if (sliceParams.action === 'saveas') { sliceName = this.state.newSliceName; if (sliceName === '') { - this.setState({ alert: 'Please enter a slice name' }); + this.setState({ alert: t('Please enter a slice name') }); return; } sliceParams.slice_name = sliceName; @@ -84,7 +85,7 @@ class SaveModal extends React.Component { case ('existing'): dashboard = this.state.saveToDashboardId; if (!dashboard) { - this.setState({ alert: 'Please select a dashboard' }); + this.setState({ alert: t('Please select a dashboard') }); return; } sliceParams.save_to_dashboard_id = dashboard; @@ -92,7 +93,7 @@ class SaveModal extends React.Component { case ('new'): dashboard = this.state.newDashboardName; if (dashboard === '') { - this.setState({ alert: 'Please enter a dashboard name' }); + this.setState({ alert: t('Please enter a dashboard name') }); return; } sliceParams.new_dashboard_name = dashboard; @@ -130,7 +131,7 @@ class SaveModal extends React.Component { > - Save A Slice + {t('Save A Slice')} @@ -151,7 +152,7 @@ class SaveModal extends React.Component { checked={this.state.action === 'overwrite'} onChange={this.changeAction.bind(this, 'overwrite')} > - {`Overwrite slice ${this.props.slice.slice_name}`} + {`${t('Overwrite slice')} ${this.props.slice.slice_name}`} } @@ -160,11 +161,11 @@ class SaveModal extends React.Component { inline checked={this.state.action === 'saveas'} onChange={this.changeAction.bind(this, 'saveas')} - > Save as   + > {t('Save as')}   @@ -177,7 +178,7 @@ class SaveModal extends React.Component { checked={this.state.addToDash === 'noSave'} onChange={this.changeDash.bind(this, 'noSave')} > - Do not add to a dashboard + {t('Do not add to a dashboard')} - Add slice to existing dashboard + {t('Add slice to existing dashboard')} + @@ -215,7 +217,7 @@ class SaveModal extends React.Component { className="btn pull-left" onClick={this.saveOrOverwrite.bind(this, false)} > - Save + {t('Save')} diff --git a/superset/assets/javascripts/explore/stores/language.js b/superset/assets/javascripts/explore/stores/language.js new file mode 100644 index 000000000000..23898a8d1843 --- /dev/null +++ b/superset/assets/javascripts/explore/stores/language.js @@ -0,0 +1,34 @@ + +const $ = window.$ = require('jquery'); + +export function getLanguage() { + function getLocale() { + const locale = $.ajax({ + url: '/superset/rest/api/getLocale', + async: false, + }); + return locale.responseText; + } + switch (getLocale()) { + case 'en': + return 'es'; + case 'fr': + return 'fr'; + case 'it': + return 'it'; + case 'zh': + return 'zh'; + default: + return 'es'; + } +} + +export function getTranslate() { + const translate = $.ajax({ + url: '/superset/rest/api/getTranslate', + async: false, + }); + console.log(translate); + console.log(translate.responseText); + return translate.responseText; +} diff --git a/superset/assets/javascripts/locale.jsx b/superset/assets/javascripts/locale.jsx new file mode 100644 index 000000000000..01054480d4ce --- /dev/null +++ b/superset/assets/javascripts/locale.jsx @@ -0,0 +1,193 @@ +import Jed from 'jed'; +import React from 'react'; +import { getTranslations } from './translations'; +import { sprintf } from 'sprintf-js'; +import { getLanguage } from './explore/stores/language'; + +let LOCALE_DEBUG = false; + +if (sessionStorage && sessionStorage.getItem('localeDebug') == '1') { + LOCALE_DEBUG = true; +} + +export function setLocaleDebug(value) { + sessionStorage.setItem('localeDebug', value ? '1' : '0'); +} + +let i18n = null; + +export function setLocale(locale) { + let translations = getTranslations(locale); + i18n = new Jed({ + 'domain' : 'superset', + 'missing_key_callback' : function(key) { + // console.error(key) + }, + 'locale_data': { + 'superset': translations + } + }); +} + +setLocale(getLanguage()); +// setLocale('zh'); + +function formatForReact(formatString, args) { + let rv = []; + let cursor = 0; + sprintf.parse(formatString).forEach((match, 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 { + match[2] = null; + match[1] = 1; + rv.push( + {sprintf.format([match], [null, arg])} + ); + } + } + }); + 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((key) => { + return React.isValidElement(args[0][key]); + }); + } + return false; +} + +export function parseComponentTemplate(string) { + let rv = {}; + function process(startPos, group, inGroup) { + let regex = /\[(.*?)(:|\])|\]/g; + let match; + let buf = []; + let satisfied = false; + let pos = regex.lastIndex = startPos; + while ((match = regex.exec(string)) !== null) { + let 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]}); + } + let endPos = regex.lastIndex; + if (!satisfied) { + let 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) { + let children = []; + (template[group] || []).forEach((item) => { + if (typeof item === 'string') { + children.push({item}); + } else { + children.push(renderGroup(item.group)); + } + }); + let reference = components[group] || ; + if (!React.isValidElement(reference)) { + reference = {reference}; + } + if (children.length > 0) { + return React.cloneElement(reference, {key: idx++}, children); + } else { + return React.cloneElement(reference, {key: idx++}); + } + } + return renderGroup('root'); +} + +function mark(rv) { + if (!LOCALE_DEBUG) { + return rv; + } + let proxy = { + $$typeof: Symbol.for('react.element'), + type: 'span', + key: null, + ref: null, + props: { + className: 'translation-wrapper', + children: typeof rv === 'array' ? rv : [rv] + }, + _owner: null, + _store: {} + }; + proxy.toString = function() { + return '🇦🇹' + rv + '🇦🇹'; + }; + return proxy; +} + +export function format(formatString, args) { + if (argsInvolveReact(args)) { + return formatForReact(formatString, args); + } else { + return sprintf(formatString, ...args); + } +} + +export function gettext(string, ...args) { + let rv = i18n.gettext(string); + if (args.length > 0) { + rv = format(rv, args); + } + return mark(rv); +} + +export function ngettext(singular, plural, ...args) { + return mark(format(i18n.ngettext(singular, plural, args[0] || 0), args)); +} + +export function gettextComponentTemplate(template, components) { + let tmpl = parseComponentTemplate(i18n.gettext(template)); + return mark(renderComponentTemplate(tmpl, components)); +} + +export const t = gettext; +export const tn = ngettext; +export const tct = gettextComponentTemplate; diff --git a/superset/assets/javascripts/translations.jsx b/superset/assets/javascripts/translations.jsx new file mode 100644 index 000000000000..8853cb49ff17 --- /dev/null +++ b/superset/assets/javascripts/translations.jsx @@ -0,0 +1,34 @@ +import {_} from 'underscore'; + +const catalogs = (function() { + let info = require('../../translations/catalogs.json'); + return info.supported_locales; +})(); + +export const translations = (function() { + let ctx = require.context('../../translations/', true, /\.po$/); + let rv = {}; + ctx.keys().forEach((translation) => { + let langCode = translation.match(/([a-zA-Z_]+)/)[1]; + if (_.contains(catalogs, langCode)) { + rv[dirname_to_locale(langCode)] = ctx(translation); + } + }); + return rv; +})(); + +export function getTranslations(language) { + return translations[language] || translations['zh']; +} + +export function translationsExist(language) { + return translations[language] !== undefined; +} + +function dirname_to_locale(dir_name) { + if (dir_name.indexOf('_') >= 0) { + let locale_array = dir_name.split('_'); + dir_name = locale_array[0] + '-' + locale_array[1].toLowerCase(); + } + return dir_name +} diff --git a/superset/assets/package.json b/superset/assets/package.json index 5d5022def10b..c5136b7c6c42 100644 --- a/superset/assets/package.json +++ b/superset/assets/package.json @@ -53,6 +53,7 @@ "datatables.net": "^1.10.13", "datatables.net-bs": "^1.10.12", "immutable": "^3.8.1", + "jed": "^1.1.1", "jquery": "^3.2.1", "jsdom": "9.12.0", "lodash.throttle": "^4.1.1", @@ -60,6 +61,7 @@ "moment": "^2.14.1", "mustache": "^2.2.1", "nvd3": "1.8.5", + "po-catalog-loader": "^1.2.0", "prop-types": "^15.5.8", "react": "^15.5.1", "react-ace": "^5.0.1", @@ -84,7 +86,9 @@ "redux-localstorage": "^0.4.1", "redux-thunk": "^2.1.0", "shortid": "^2.2.6", + "sprintfjs": "^1.2.6", "supercluster": "https://github.com/georgeke/supercluster/tarball/ac3492737e7ce98e07af679623aad452373bbc40", + "underscore": "^1.8.3", "urijs": "^1.18.10", "viewport-mercator-project": "^2.1.0" }, diff --git a/superset/assets/webpack.config.js b/superset/assets/webpack.config.js index be8c8c983b9c..e2f33bc71afb 100644 --- a/superset/assets/webpack.config.js +++ b/superset/assets/webpack.config.js @@ -107,6 +107,14 @@ const config = { include: APP_DIR + '/node_modules/mapbox-gl/js/render/painter/use_program.js', loader: 'transform/cacheable?brfs', }, + { + test: /\.po$/, + loader: 'po-catalog-loader', + query: { + referenceExtensions: ['.js', '.jsx'], + domain: 'superset' + } + }, ], }, externals: { diff --git a/superset/config.py b/superset/config.py index 6c38fa2f76b5..394c7f215e01 100644 --- a/superset/config.py +++ b/superset/config.py @@ -144,8 +144,8 @@ LANGUAGES = { 'en': {'flag': 'us', 'name': 'English'}, 'it': {'flag': 'it', 'name': 'Italian'}, - # 'fr': {'flag': 'fr', 'name': 'French'}, - # 'zh': {'flag': 'cn', 'name': 'Chinese'}, + 'fr': {'flag': 'fr', 'name': 'French'}, + 'zh': {'flag': 'cn', 'name': 'Chinese'}, } # --------------------------------------------------- # Image and file configuration diff --git a/superset/translations/catalogs.json b/superset/translations/catalogs.json new file mode 100644 index 000000000000..b4fe4dd395c5 --- /dev/null +++ b/superset/translations/catalogs.json @@ -0,0 +1,8 @@ +{ + "supported_locales": [ + "es", + "fr", + "it", + "zh" + ] +} diff --git a/superset/translations/es/LC_MESSAGES/messages.mo b/superset/translations/es/LC_MESSAGES/messages.mo index 3da6a8e16b57..580a31fcab3e 100644 Binary files a/superset/translations/es/LC_MESSAGES/messages.mo and b/superset/translations/es/LC_MESSAGES/messages.mo differ diff --git a/superset/translations/es/LC_MESSAGES/messages.po b/superset/translations/es/LC_MESSAGES/messages.po index 16e4d98dad98..569c5b9a16bd 100644 --- a/superset/translations/es/LC_MESSAGES/messages.po +++ b/superset/translations/es/LC_MESSAGES/messages.po @@ -917,6 +917,58 @@ msgstr "" msgid "Saved Queries" msgstr "" +#: superset/assets/javascripts/explore/components/SaveModal.jsx:73 +msgid "Please enter a slice name" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:88 +msgid "Please select a dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:96 +msgid "Please enter a dashboard name" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:134 +msgid "Save A Slice" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:155 +msgid "Overwrite slice" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:164 +msgid "Save as" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:168 +msgid "[slice name]" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:181 +msgid "Do not add to a dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:189 +msgid "Add slice to existing dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:203 +msgid "Add to new dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:208 +msgid "[dashboard name]" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:220 +msgid "Save" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:229 +msgid "Save & go to dashboard" +msgstr "" + #~ msgid "Databases" #~ msgstr "" diff --git a/superset/translations/fr/LC_MESSAGES/messages.mo b/superset/translations/fr/LC_MESSAGES/messages.mo index ce19085d4ae0..4fa36d625378 100644 Binary files a/superset/translations/fr/LC_MESSAGES/messages.mo and b/superset/translations/fr/LC_MESSAGES/messages.mo differ diff --git a/superset/translations/fr/LC_MESSAGES/messages.po b/superset/translations/fr/LC_MESSAGES/messages.po index 15b8fc40321f..a45d5bacd198 100644 --- a/superset/translations/fr/LC_MESSAGES/messages.po +++ b/superset/translations/fr/LC_MESSAGES/messages.po @@ -917,6 +917,58 @@ msgstr "" msgid "Saved Queries" msgstr "" +#: superset/assets/javascripts/explore/components/SaveModal.jsx:73 +msgid "Please enter a slice name" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:88 +msgid "Please select a dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:96 +msgid "Please enter a dashboard name" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:134 +msgid "Save A Slice" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:155 +msgid "Overwrite slice" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:164 +msgid "Save as" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:168 +msgid "[slice name]" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:181 +msgid "Do not add to a dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:189 +msgid "Add slice to existing dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:203 +msgid "Add to new dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:208 +msgid "[dashboard name]" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:220 +msgid "Save" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:229 +msgid "Save & go to dashboard" +msgstr "" + #~ msgid "Databases" #~ msgstr "" diff --git a/superset/translations/it/LC_MESSAGES/messages.mo b/superset/translations/it/LC_MESSAGES/messages.mo index c3583a7db5a8..b38fc85fcb5d 100644 Binary files a/superset/translations/it/LC_MESSAGES/messages.mo and b/superset/translations/it/LC_MESSAGES/messages.mo differ diff --git a/superset/translations/it/LC_MESSAGES/messages.po b/superset/translations/it/LC_MESSAGES/messages.po index ea03d006fa5b..2ae9574b2b91 100644 --- a/superset/translations/it/LC_MESSAGES/messages.po +++ b/superset/translations/it/LC_MESSAGES/messages.po @@ -981,3 +981,55 @@ msgstr "" msgid "Saved Queries" msgstr "" +#: superset/assets/javascripts/explore/components/SaveModal.jsx:73 +msgid "Please enter a slice name" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:88 +msgid "Please select a dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:96 +msgid "Please enter a dashboard name" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:134 +msgid "Save A Slice" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:155 +msgid "Overwrite slice" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:164 +msgid "Save as" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:168 +msgid "[slice name]" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:181 +msgid "Do not add to a dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:189 +msgid "Add slice to existing dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:203 +msgid "Add to new dashboard" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:208 +msgid "[dashboard name]" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:220 +msgid "Save" +msgstr "" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:229 +msgid "Save & go to dashboard" +msgstr "" + diff --git a/superset/translations/zh/LC_MESSAGES/messages.mo b/superset/translations/zh/LC_MESSAGES/messages.mo index d35a0d6341fe..a23a0d28bf58 100644 Binary files a/superset/translations/zh/LC_MESSAGES/messages.mo and b/superset/translations/zh/LC_MESSAGES/messages.mo differ diff --git a/superset/translations/zh/LC_MESSAGES/messages.po b/superset/translations/zh/LC_MESSAGES/messages.po index 4c72d20b2f97..6dbc3202a69b 100644 --- a/superset/translations/zh/LC_MESSAGES/messages.po +++ b/superset/translations/zh/LC_MESSAGES/messages.po @@ -917,3 +917,55 @@ msgstr "查询" msgid "Saved Queries" msgstr "已保存查询" +#: superset/assets/javascripts/explore/components/SaveModal.jsx:73 +msgid "Please enter a slice name" +msgstr "请输入切片名称" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:88 +msgid "Please select a dashboard" +msgstr "请选择一个仪表盘" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:96 +msgid "Please enter a dashboard name" +msgstr "请输入仪表盘名称" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:134 +msgid "Save A Slice" +msgstr "保存一个切片" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:155 +msgid "Overwrite slice" +msgstr "覆盖切片" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:164 +msgid "Save as" +msgstr "另存为" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:168 +msgid "[slice name]" +msgstr "[切片名称]" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:181 +msgid "Do not add to a dashboard" +msgstr "不要添加到仪表盘" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:189 +msgid "Add slice to existing dashboard" +msgstr "将切片添加到现有仪表盘" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:203 +msgid "Add to new dashboard" +msgstr "添加到新的仪表盘" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:208 +msgid "[dashboard name]" +msgstr "[仪表盘名称]" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:220 +msgid "Save" +msgstr "保存" + +#: superset/assets/javascripts/explore/components/SaveModal.jsx:229 +msgid "Save & go to dashboard" +msgstr "保存并转到仪表盘" + diff --git a/superset/views/core.py b/superset/views/core.py index dec49edd23a3..008a2685494c 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -26,6 +26,7 @@ from flask_babel import gettext as __ from flask_babel import lazy_gettext as _ +from flask.ext import babel from sqlalchemy import create_engine from werkzeug.routing import BaseConverter @@ -2260,6 +2261,11 @@ def sqllab(self): 'superset/sqllab.html', bootstrap_data=json.dumps(d, default=utils.json_iso_dttm_ser) ) + + @has_access + @expose("/rest/api/getLocale", methods=['GET', 'POST']) + def getLocale(self): + return str(babel.get_locale()) appbuilder.add_view_no_menu(Superset)