js translation -- performance improvment (#3390)

* Chinese page

* Using react-intl-universal to improve multi language in react page

* Using react-intl-universal to improve multi language in react page

* react_intl_universal

* change

* change

* change

* change

* change

* change

* change

* merge

* multiple page in js

* merge

* merge

* merge

* merge

* Js Translations

* JS Translation

* JS Translations

* Js translation

* JS translations

* JS translations

* Js translaion

* JS en Translation

* JS Translation

* upgrade document

Fixing the damn build (#3179)

* Fixing the build

* Going deeper

[bugfix] only filterable columns should show up in FilterBox list (#3105)

* [bugfix] only filterable columns should show up in FilterBox list

* Touchups

Datasource cannot be empty (#3035)

add title description to model view (#3045)

* add title description to model view

* add missing import

Add 'show/hide totals' option to pivot table vis (#3101)

[bugfix] numeric value for date fields in table viz (#3036)

Bug was present only when using the NOT GROUPED BY option

fixes https://github.com/ApacheInfra/superset/issues/3027

fix hive.fetch_logs (#2968)

add Zalando to the list of organizations (#3171)

docs: fixup installation examples code indentation (#3169)

[bugfix] fix bar order (#3180)

[bugfix] visualize flow error: 'Metric x is not valid' (#3181)

The metric name in the frontend doesn't match the one generated on the
backend. It turns out the explore view will default to the first
metric so specifying one isn't needed.

Fix the segment interval for pulling metadata (#3174)

The end of the interval would be on the truncated today date, which
means that you will exclude today. If your realtime ingestion job
runs shorter than a day, the metadata cannot be pulled from the
druid cluster.

Bump cryptography to 1.9 (#3065)

As 1.7.2 doesn't compile here with openssl 1.1.0f

Escaping the user's SQL in the explore view (#3186)

* Escaping the user's SQL in the explore view

When executing SQL from SQL Lab, we use a lower level API to the
database which doesn't require escaping the SQL. When going through
the explore view, the stack chain leading to the same method may need
escaping depending on how the DBAPI driver is written, and that is the
case for Presto (and perhaps other drivers).

* Using regex to avoid doubling doubles

[sqllab] improve Hive support (#3187)

* [sqllab] improve Hive support

* Fix "Transport not open" bug
* Getting progress bar to show
* Bump pyhive to 0.4.0
* Getting [Track Job] button to show

* Fix testzz

Add BigQuery engine specifications (#3193)

As contributed by @mxmzdlv on issue #945

[bugfix] fix merge conflict that broke Hive support (#3196)

Adding 'apache' to docs (#3194)

[druid] Allow custom druid postaggregators (#3146)

* [druid] Allow custom druid postaggregators

Also, fix the postaggregation for approxHistogram quantiles so it adds
the dependent field and that can show up in the graphs/tables.

In general, postAggregators add significant power, we should probably
support including custom postAggregators. Plywood has standard
postAggregators here, and a customAggregator escape hatch that allows
you to define custom postAggregators.

This commit adds a similar capability for Superset and a additional
field/fields/fieldName breakdown of the typical naming for dependent
aggregations, which should make it significantly easier to develop
approxHistogram and custom postAggregation-required dashboards.

* [druid] Minor style cleanup in tests file.

* [druid] Apply code review suggestions

* break out CustomPostAggregator into separate class. This just cleans
  up the creation of the postaggregator a little bit.
* minor style issues.
* move the function around so the git diff is more readable

add combine config for metrics in pivot table (#3086)

* add combine config for metrics in pivot table

* change method to stack/unstack

* update backendSync

Autofocus search input in VizTypeControl modal onEnter (#2929)

Speed up JS build time (#3203)

Also bumping a few related libs

JS Translation

JS translations

js translation

fix issue 3204 (#3205)

[bugfix] capture Hive job_id pre-url transformation (#3213)

js translation

fix issue 3204 (#3205)

[bugfix] capture Hive job_id pre-url transformation (#3213)

[docs] update url in CONTRIBUTING.md (#3212)

[sqllab/cosmetics] add margin-top for labels in query history (#3222)

[explore] nvd3 sort values in rich tooltip (#3197)

[sqllab] fix UI shows 'The query returned no results' momentarily (#3214)

this is visible when running async queries between the fetching and
success state as the rows are getting cached in the component

[explore] DatasourceControl to pick datasource in modal (#3210)

* [explore] DatasourceControl to pick datasource in modal

Makes it easier to change datasource, also makes it such that the list
of all datasources doesn't need to be loaded upfront.

* Adding more metadata

* Js translation

* js tran

* js trans

* js trans

* js tran

* js trans

* js trans

* js tran

* js translation

* js trans

* js translation

* try load language pack async

* Backend translations things

* create language pack inside common data

* performance improvement for js i18n.

- js bundle should not contain localized content
- we populate translation content from server-side, in boostrap.common.language_pack
- in client-side, use promise to wrap around translation content. text will be translated after translation content arrived/parsed.
- fix linting

* fix Timer unit test

* 1. add global hook for all tests, to make translation pack avaialble before each test starts.
2. fix unit test for Timer component
3. remove noused method get_locale, and modules
4. fix page reload after user change page language

* parse and build i18n dictionary as a module

* fix sync-backend task, which should run without DOM
This commit is contained in:
Grace Guo 2017-09-20 12:37:33 -07:00 committed by Maxime Beauchemin
parent 1cf634afa2
commit 9af34ba51c
92 changed files with 14985 additions and 3239 deletions

View File

@ -331,6 +331,8 @@ key is to instrument the strings that need translation using
a module, all you have to do is to `_("Wrap your strings")` using the
underscore `_` "function".
We use `import {t, tn, TCT} from locales;` in js, JSX file, locales is in `./superset/assets/javascripts/` directory.
To enable changing language in your environment, you can simply add the
`LANGUAGES` parameter to your `superset_config.py`. Having more than one
options here will add a language selection dropdown on the right side of the
@ -342,6 +344,10 @@ navigation bar.
'zh': {'flag': 'cn', 'name': 'Chinese'},
}
We need to extract the string to be translated, run the following command:
pybabel extract -F ./babel/babel.cfg -k _ -k __ -k t -k tn -k tct -o ./babel/messages.pot .
As per the [Flask AppBuilder documentation] about translation, to create a
new language dictionary, run the following command:
@ -358,6 +364,14 @@ to take effect, they need to be compiled using this command:
fabmanager babel-compile --target superset/translations/
In the case of JS translation, we need to convert the PO file into a JSON file, and we need the global download of the npm package po2json.
We need to be compiled using this command:
npm install po2json -g
Execute this command to convert the en PO file into a json file:
po2json -d superset -f jed1.x superset/translations/en/LC_MESSAGES/messages.po superset/translations/en/LC_MESSAGES/messages.json
## Adding new datasources

View File

@ -1,4 +1,8 @@
[ignore: superset/assets/node_modules/**]
[python: superset/**.py]
[jinja2: superset/**/templates/**.html]
[javascript: superset/assets/javascripts/**.js]
[javascript: superset/assets/javascripts/**.jsx]
[javascript: superset/assets/visualizations/**.js]
[javascript: superset/assets/visualizations/**.jsx]
encoding = utf-8

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
/* global notify */
import shortid from 'shortid';
import { now } from '../modules/dates';
import { t } from '../locales';
const $ = require('jquery');
@ -53,8 +54,8 @@ export function saveQuery(query) {
type: 'POST',
url,
data: query,
success: () => notify.success('Your query was saved'),
error: () => notify.error('Your query could not be saved'),
success: () => notify.success(t('Your query was saved')),
error: () => notify.error(t('Your query could not be saved')),
dataType: 'json',
});
return { type: SAVE_QUERY };
@ -107,7 +108,7 @@ export function fetchQueryResults(query) {
dispatch(querySuccess(query, results));
},
error(err) {
let msg = 'Failed at retrieving results from the results backend';
let msg = t('Failed at retrieving results from the results backend');
if (err.responseJSON && err.responseJSON.error) {
msg = err.responseJSON.error;
}
@ -153,12 +154,12 @@ export function runQuery(query) {
}
}
if (textStatus === 'error' && errorThrown === '') {
msg = 'Could not connect to server';
msg = t('Could not connect to server');
} else if (msg === null) {
msg = `[${textStatus}] ${errorThrown}`;
}
if (msg.indexOf('CSRF token') > 0) {
msg = 'Your session timed out, please refresh your page and try again.';
msg = t('Your session timed out, please refresh your page and try again.');
}
dispatch(queryFailed(query, msg));
},
@ -177,10 +178,10 @@ export function postStopQuery(query) {
url: stopQueryUrl,
data: stopQueryRequestData,
success() {
notify.success('Query was stopped.');
notify.success(t('Query was stopped.'));
},
error() {
notify.error('Failed at stopping query.');
notify.error(t('Failed at stopping query.'));
},
});
};
@ -293,7 +294,7 @@ export function addTable(query, tableName, schemaName) {
isMetadataLoading: false,
});
dispatch(mergeTable(newTable));
notify.error('Error occurred while fetching table metadata');
notify.error(t('Error occurred while fetching table metadata'));
});
url = `/superset/extra_table_metadata/${query.dbId}/${tableName}/${schemaName}/`;
@ -306,7 +307,7 @@ export function addTable(query, tableName, schemaName) {
isExtraMetadataLoading: false,
});
dispatch(mergeTable(newTable));
notify.error('Error occurred while fetching table metadata');
notify.error(t('Error occurred while fetching table metadata'));
});
};
}
@ -360,7 +361,7 @@ export function popStoredQuery(urlId) {
success: (data) => {
const newQuery = JSON.parse(data);
const queryEditorProps = {
title: newQuery.title ? newQuery.title : 'shared query',
title: newQuery.title ? newQuery.title : t('shared query'),
dbId: newQuery.dbId ? parseInt(newQuery.dbId, 10) : null,
schema: newQuery.schema ? newQuery.schema : null,
autorun: newQuery.autorun ? newQuery.autorun : false,
@ -368,7 +369,7 @@ export function popStoredQuery(urlId) {
};
dispatch(addQueryEditor(queryEditorProps));
},
error: () => notify.error("The query couldn't be loaded"),
error: () => notify.error(t('The query couldn\'t be loaded')),
});
};
}
@ -388,7 +389,7 @@ export function popSavedQuery(saveQueryId) {
};
dispatch(addQueryEditor(queryEditorProps));
},
error: () => notify.error("The query couldn't be loaded"),
error: () => notify.error(t('The query couldn\'t be loaded')),
});
};
}
@ -421,7 +422,7 @@ export function createDatasource(vizOptions, context) {
dispatch(createDatasourceSuccess(resp));
},
error: () => {
dispatch(createDatasourceFailed('An error occurred while creating the data source'));
dispatch(createDatasourceFailed(t('An error occurred while creating the data source')));
},
});
};

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import CopyToClipboard from '../../components/CopyToClipboard';
import { storeQuery } from '../../../utils/common';
import { t } from '../../locales';
const propTypes = {
queryEditor: PropTypes.object.isRequired,
@ -26,10 +27,10 @@ export default class CopyQueryTabUrl extends React.PureComponent {
inMenu
copyNode={(
<div>
<i className="fa fa-clipboard" /> <span>share query</span>
<i className="fa fa-clipboard" /> <span>{t('share query')}</span>
</div>
)}
tooltipText="copy URL to clipboard"
tooltipText={t('copy URL to clipboard')}
shouldShowText={false}
getText={this.getUrl.bind(this)}
/>

View File

@ -6,6 +6,7 @@ import sql from 'react-syntax-highlighter/dist/languages/sql';
import github from 'react-syntax-highlighter/dist/styles/github';
import ModalTrigger from '../../components/ModalTrigger';
import { t } from '../../locales';
registerLanguage('sql', sql);
@ -57,7 +58,7 @@ class HighlightedSql extends React.Component {
if (this.props.rawSql && this.props.rawSql !== this.props.sql) {
rawSql = (
<div>
<h4>Raw SQL</h4>
<h4>{t('Raw SQL')}</h4>
<SyntaxHighlighter language="sql" style={github}>
{this.props.rawSql}
</SyntaxHighlighter>
@ -67,7 +68,7 @@ class HighlightedSql extends React.Component {
this.setState({
modalBody: (
<div>
<h4>Source SQL</h4>
<h4>{t('Source SQL')}</h4>
<SyntaxHighlighter language="sql" style={github}>
{this.props.sql}
</SyntaxHighlighter>
@ -79,7 +80,7 @@ class HighlightedSql extends React.Component {
render() {
return (
<ModalTrigger
modalTitle="SQL"
modalTitle={t('SQL')}
triggerNode={this.triggerNode()}
modalBody={this.state.modalBody}
beforeOpen={this.generateModal.bind(this)}

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Alert } from 'react-bootstrap';
import QueryTable from './QueryTable';
import { t } from '../../locales';
const propTypes = {
queries: PropTypes.array.isRequired,
@ -24,7 +25,7 @@ const QueryHistory = (props) => {
}
return (
<Alert bsStyle="info">
No query history yet...
{t('No query history yet...')}
</Alert>
);
};

View File

@ -7,6 +7,7 @@ import { now, epochTimeXHoursAgo,
epochTimeXDaysAgo, epochTimeXYearsAgo } from '../../modules/dates';
import { STATUS_OPTIONS, TIME_OPTIONS } from '../constants';
import AsyncSelect from '../../components/AsyncSelect';
import { t } from '../../locales';
const $ = window.$ = require('jquery');
@ -102,7 +103,7 @@ class QuerySearch extends React.PureComponent {
if (data.result.length === 0) {
this.props.actions.addAlert({
bsStyle: 'danger',
msg: "It seems you don't have access to any database",
msg: t('It seems you don\'t have access to any database'),
});
}
return options;
@ -150,15 +151,15 @@ class QuerySearch extends React.PureComponent {
type="text"
onChange={this.changeSearch.bind(this)}
className="form-control input-sm"
placeholder="Search Results"
placeholder={t('Search Results')}
/>
</div>
<div className="col-sm-4 search-date-filter-container">
<Select
name="select-from"
placeholder="[From]-"
placeholder={t('[From]-')}
options={TIME_OPTIONS
.slice(1, TIME_OPTIONS.length).map(t => ({ value: t, label: t }))}
.slice(1, TIME_OPTIONS.length).map(xt => ({ value: xt, label: xt }))}
value={this.state.from}
autosize={false}
onChange={this.changeFrom.bind(this)}
@ -166,8 +167,8 @@ class QuerySearch extends React.PureComponent {
<Select
name="select-to"
placeholder="[To]-"
options={TIME_OPTIONS.map(t => ({ value: t, label: t }))}
placeholder={t('[To]-')}
options={TIME_OPTIONS.map(xt => ({ value: xt, label: xt }))}
value={this.state.to}
autosize={false}
onChange={this.changeTo.bind(this)}
@ -175,7 +176,7 @@ class QuerySearch extends React.PureComponent {
<Select
name="select-status"
placeholder="[Query Status]"
placeholder={t('[Query Status]')}
options={STATUS_OPTIONS.map(s => ({ value: s, label: s }))}
value={this.state.status}
isLoading={false}
@ -184,7 +185,7 @@ class QuerySearch extends React.PureComponent {
/>
<Button bsSize="small" bsStyle="success" onClick={this.refreshQueries.bind(this)}>
Search
{t('Search')}
</Button>
</div>
</div>

View File

@ -12,6 +12,7 @@ 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,
@ -45,7 +46,7 @@ class QueryTable extends React.PureComponent {
openQuery(dbId, schema, sql) {
const newQuery = {
dbId,
title: 'Untitled Query',
title: t('Untitled Query'),
schema,
sql,
};
@ -110,7 +111,7 @@ class QueryTable extends React.PureComponent {
className="btn btn-link btn-xs"
onClick={this.openQuery.bind(this, q.dbId, q.schema, q.sql)}
>
<i className="fa fa-external-link" />Open in SQL Editor
<i className="fa fa-external-link" />{t('Open in SQL Editor')}
</button>
</div>
);
@ -129,10 +130,10 @@ class QueryTable extends React.PureComponent {
bsStyle="info"
style={{ cursor: 'pointer' }}
>
view results
{t('view results')}
</Label>
)}
modalTitle={'Data preview'}
modalTitle={t('Data preview')}
beforeOpen={this.openAsyncResults.bind(this, query)}
onExit={this.clearQueryResults.bind(this, query)}
modalBody={
@ -172,24 +173,24 @@ class QueryTable extends React.PureComponent {
<div style={{ width: '75px' }}>
<Link
className="fa fa-line-chart m-r-3"
tooltip="Visualize the data out of this query"
tooltip={t('Visualize the data out of this query')}
onClick={this.showVisualizeModal.bind(this, query)}
/>
<Link
className="fa fa-pencil m-r-3"
onClick={this.restoreSql.bind(this, query)}
tooltip="Overwrite text in editor with a query on this table"
tooltip={t('Overwrite text in editor with a query on this table')}
placement="top"
/>
<Link
className="fa fa-plus-circle m-r-3"
onClick={this.openQueryInNewTab.bind(this, query)}
tooltip="Run query in a new tab"
tooltip={t('Run query in a new tab')}
placement="top"
/>
<Link
className="fa fa-trash m-r-3"
tooltip="Remove query from log"
tooltip={t('Remove query from log')}
onClick={this.removeQuery.bind(this, query)}
/>
</div>

View File

@ -7,6 +7,7 @@ import VisualizeModal from './VisualizeModal';
import HighlightedSql from './HighlightedSql';
import FilterableTable from '../../components/FilterableTable/FilterableTable';
import QueryStateLabel from './QueryStateLabel';
import { t } from '../../locales';
const propTypes = {
actions: PropTypes.object,
@ -63,7 +64,7 @@ export default class ResultSet extends React.PureComponent {
if (this.props.csv) {
csvButton = (
<Button bsSize="small" href={'/superset/csv/' + this.props.query.id}>
<i className="fa fa-file-text-o" /> .CSV
<i className="fa fa-file-text-o" /> {t('.CSV')}
</Button>
);
}
@ -74,7 +75,7 @@ export default class ResultSet extends React.PureComponent {
bsSize="small"
onClick={this.showModal.bind(this)}
>
<i className="fa fa-line-chart m-l-1" /> Visualize
<i className="fa fa-line-chart m-l-1" /> {t('Visualize')}
</Button>
);
}
@ -85,7 +86,7 @@ export default class ResultSet extends React.PureComponent {
type="text"
onChange={this.changeSearch.bind(this)}
className="form-control input-sm"
placeholder="Search Results"
placeholder={t('Search Results')}
/>
);
}
@ -158,14 +159,14 @@ export default class ResultSet extends React.PureComponent {
return (
<div>
<Alert bsStyle="info">
Table [<strong>{query.tempTable}</strong>] was
created &nbsp;
{t('Table')} [<strong>{query.tempTable}</strong>] {t('was ' +
'created')} &nbsp;
<Button
bsSize="small"
className="m-r-5"
onClick={this.popSelectStar.bind(this)}
>
Query in a new tab
{t('Query in a new tab')}
</Button>
</Alert>
</div>);
@ -206,7 +207,7 @@ export default class ResultSet extends React.PureComponent {
bsStyle="primary"
onClick={this.reFetchQueryResults.bind(this, query)}
>
Fetch data preview
{t('Fetch data preview')}
</Button>
);
}
@ -226,13 +227,13 @@ export default class ResultSet extends React.PureComponent {
bsSize="small"
onClick={() => { window.open(query.trackingUrl); }}
>
Track Job
{t('Track Job')}
</Button>
);
}
return (
<div>
<img className="loading" alt="Loading..." src="/static/assets/images/loading.gif" />
<img className="loading" alt={t('Loading...')} src="/static/assets/images/loading.gif" />
<QueryStateLabel query={query} />
{progressBar}
<div>

View File

@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import Button from '../../components/Button';
import { t } from '../../locales';
const propTypes = {
allowAsync: PropTypes.bool.isRequired,
@ -15,10 +16,10 @@ const defaultProps = {
};
export default function RunQueryActionButton(props) {
const runBtnText = props.selectedText ? 'Run Selected Query' : 'Run Query';
const runBtnText = props.selectedText ? t('Run Selected Query') : t('Run Query');
const btnStyle = props.selectedText ? 'warning' : 'primary';
const shouldShowStopBtn = ['running', 'pending'].indexOf(props.queryState) > -1;
const tooltip = 'shortcut: [alt+enter]';
const asyncToolTip = t('Run query asynchronously');
const commonBtnProps = {
bsSize: 'small',
@ -31,7 +32,7 @@ export default function RunQueryActionButton(props) {
{...commonBtnProps}
onClick={() => props.runQuery(false)}
key="run-btn"
tooltip={tooltip}
tooltip={asyncToolTip}
>
<i className="fa fa-refresh" /> {runBtnText}
</Button>
@ -42,7 +43,7 @@ export default function RunQueryActionButton(props) {
{...commonBtnProps}
onClick={() => props.runQuery(true)}
key="run-async-btn"
tooltip={tooltip}
tooltip={asyncToolTip}
>
<i className="fa fa-table" /> {runBtnText}
</Button>
@ -53,7 +54,7 @@ export default function RunQueryActionButton(props) {
{...commonBtnProps}
onClick={props.stopQuery}
>
<i className="fa fa-stop" /> Stop
<i className="fa fa-stop" /> {t('Stop')}
</Button>
);

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FormControl, FormGroup, Overlay, Popover, Row, Col } from 'react-bootstrap';
import Button from '../../components/Button';
import { t } from '../../locales';
const propTypes = {
defaultLabel: PropTypes.string,
@ -12,7 +13,7 @@ const propTypes = {
onSave: PropTypes.func,
};
const defaultProps = {
defaultLabel: 'Undefined',
defaultLabel: t('Undefined'),
animation: true,
onSave: () => {},
};
@ -62,12 +63,12 @@ class SaveQuery extends React.PureComponent {
<Col md={12}>
<small>
<label className="control-label" htmlFor="embed-height">
Label
{t('Label')}
</label>
</small>
<FormControl
type="text"
placeholder="Label for your query"
placeholder={t('Label for your query')}
value={this.state.label}
onChange={this.onLabelChange}
/>
@ -77,11 +78,11 @@ class SaveQuery extends React.PureComponent {
<Row>
<Col md={12}>
<small>
<label className="control-label" htmlFor="embed-height">Description</label>
<label className="control-label" htmlFor="embed-height">{t('Description')}</label>
</small>
<FormControl
componentClass="textarea"
placeholder="Write a description for your query"
placeholder={t('Write a description for your query')}
value={this.state.description}
onChange={this.onDescriptionChange}
/>
@ -95,10 +96,10 @@ class SaveQuery extends React.PureComponent {
onClick={this.onSave}
className="m-r-3"
>
Save
{t('Save')}
</Button>
<Button onClick={this.onCancel} className="cancelQuery">
Cancel
{t('Cancel')}
</Button>
</Col>
</Row>
@ -119,7 +120,7 @@ class SaveQuery extends React.PureComponent {
{this.renderPopover()}
</Overlay>
<Button bsSize="small" className="toggleSave" onClick={this.toggleSave}>
<i className="fa fa-save" /> Save Query
<i className="fa fa-save" /> {t('Save Query')}
</Button>
</span>
);

View File

@ -8,6 +8,7 @@ import { bindActionCreators } from 'redux';
import * as Actions from '../actions';
import QueryHistory from './QueryHistory';
import ResultSet from './ResultSet';
import { t } from '../../locales';
/*
editorQueries are queries executed by users passed from SqlEditor component
@ -48,12 +49,12 @@ class SouthPane extends React.PureComponent {
/>
);
} else {
results = <Alert bsStyle="info">Run a query to display results here</Alert>;
results = <Alert bsStyle="info">{t('Run a query to display results here')}</Alert>;
}
const dataPreviewTabs = props.dataPreviewQueries.map(query => (
<Tab
title={`Preview for ${query.tableName}`}
title={t('Preview for %s', query.tableName)}
eventKey={query.id}
key={query.id}
>
@ -77,13 +78,13 @@ class SouthPane extends React.PureComponent {
onSelect={this.switchTab.bind(this)}
>
<Tab
title="Results"
title={t('Results')}
eventKey="Results"
>
{results}
</Tab>
<Tab
title="Query History"
title={t('Query History')}
eventKey="History"
>
<div style={{ height: `${innerTabHeight}px`, overflow: 'scroll' }}>

View File

@ -22,6 +22,7 @@ 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,
@ -119,7 +120,7 @@ class SqlEditor extends React.PureComponent {
renderEditorBottomBar() {
let ctasControls;
if (this.props.database && this.props.database.allow_ctas) {
const ctasToolTip = 'Create table as with query results';
const ctasToolTip = t('Create table as with query results');
ctasControls = (
<FormGroup>
<InputGroup>
@ -127,7 +128,7 @@ class SqlEditor extends React.PureComponent {
type="text"
bsSize="small"
className="input-sm"
placeholder="new table name"
placeholder={t('new table name')}
onChange={this.ctasChanged.bind(this)}
/>
<InputGroup.Button>

View File

@ -7,6 +7,7 @@ import createFilterOptions from 'react-select-fast-filter-options';
import TableElement from './TableElement';
import AsyncSelect from '../../components/AsyncSelect';
import { t } from '../../locales';
const $ = window.$ = require('jquery');
@ -62,7 +63,7 @@ class SqlEditorLeftBar extends React.PureComponent {
if (data.result.length === 0) {
this.props.actions.addAlert({
bsStyle: 'danger',
msg: "It seems you don't have access to any database",
msg: t('It seems you don\'t have access to any database'),
});
}
return options;
@ -86,7 +87,7 @@ class SqlEditorLeftBar extends React.PureComponent {
})
.fail(() => {
this.setState({ tableLoading: false, tableOptions: [], tableLength: 0 });
notify.error('Error while fetching table list');
notify.error(t('Error while fetching table list'));
});
} else {
this.setState({ tableLoading: false, tableOptions: [], filterOptions: null });
@ -127,7 +128,7 @@ class SqlEditorLeftBar extends React.PureComponent {
})
.fail(() => {
this.setState({ schemaLoading: false, schemaOptions: [] });
notify.error('Error while fetching schema list');
notify.error(t('Error while fetching schema list'));
});
}
}
@ -149,29 +150,29 @@ class SqlEditorLeftBar extends React.PureComponent {
'_od_DatabaseAsync=asc'
}
onChange={this.onDatabaseChange.bind(this)}
onAsyncError={() => notify.error('Error while fetching database list')}
onAsyncError={() => notify.error(t('Error while fetching database list'))}
value={this.props.queryEditor.dbId}
databaseId={this.props.queryEditor.dbId}
actions={this.props.actions}
valueRenderer={o => (
<div>
<span className="text-muted">Database:</span> {o.label}
<span className="text-muted">{t('Database:')}</span> {o.label}
</div>
)}
mutator={this.dbMutator.bind(this)}
placeholder="Select a database"
placeholder={t('Select a database')}
autoSelect
/>
</div>
<div className="m-t-5">
<Select
name="select-schema"
placeholder={`Select a schema (${this.state.schemaOptions.length})`}
placeholder={t('Select a schema (%s)', this.state.schemaOptions.length)}
options={this.state.schemaOptions}
value={this.props.queryEditor.schema}
valueRenderer={o => (
<div>
<span className="text-muted">Schema:</span> {o.label}
<span className="text-muted">{t('Schema:')}</span> {o.label}
</div>
)}
isLoading={this.state.schemaLoading}
@ -186,7 +187,7 @@ class SqlEditorLeftBar extends React.PureComponent {
ref="selectTable"
isLoading={this.state.tableLoading}
value={this.state.tableName}
placeholder={`Add a table (${this.state.tableOptions.length})`}
placeholder={t('Add a table (%s)', this.state.tableOptions.length)}
autosize={false}
onChange={this.changeTable.bind(this)}
filterOptions={this.state.filterOptions}
@ -199,7 +200,7 @@ class SqlEditorLeftBar extends React.PureComponent {
name="async-select-table"
ref="selectTable"
value={this.state.tableName}
placeholder={'Type to search ...'}
placeholder={t('Type to search ...')}
autosize={false}
onChange={this.changeTable.bind(this)}
loadOptions={this.getTableNamesBySubStr.bind(this)}
@ -222,7 +223,7 @@ class SqlEditorLeftBar extends React.PureComponent {
</div>
{shouldShowReset &&
<Button bsSize="small" bsStyle="danger" onClick={this.resetState.bind(this)}>
<i className="fa fa-bomb" /> Reset State
<i className="fa fa-bomb" /> {t('Reset State')}
</Button>
}
</div>

View File

@ -9,6 +9,7 @@ import * as Actions from '../actions';
import SqlEditor from './SqlEditor';
import CopyQueryTabUrl from './CopyQueryTabUrl';
import { areArraysShallowEqual } from '../../reduxUtils';
import { t } from '../../locales';
const propTypes = {
actions: PropTypes.object.isRequired,
@ -101,7 +102,7 @@ class TabbedSqlEditors extends React.PureComponent {
}
renameTab(qe) {
/* eslint no-alert: 0 */
const newTitle = prompt('Enter a new title for the tab');
const newTitle = prompt(t('Enter a new title for the tab'));
if (newTitle) {
this.props.actions.queryEditorSetTitle(qe, newTitle);
}
@ -120,7 +121,7 @@ class TabbedSqlEditors extends React.PureComponent {
queryCount++;
const activeQueryEditor = this.activeQueryEditor();
const qe = {
title: `Untitled Query ${queryCount}`,
title: t('Untitled Query %s', queryCount),
dbId: (activeQueryEditor && activeQueryEditor.dbId) ?
activeQueryEditor.dbId :
this.props.defaultDbId,
@ -166,10 +167,10 @@ class TabbedSqlEditors extends React.PureComponent {
title=""
>
<MenuItem eventKey="1" onClick={this.removeQueryEditor.bind(this, qe)}>
<i className="fa fa-close" /> close tab
<i className="fa fa-close" /> {t('close tab')}
</MenuItem>
<MenuItem eventKey="2" onClick={this.renameTab.bind(this, qe)}>
<i className="fa fa-i-cursor" /> rename tab
<i className="fa fa-i-cursor" /> {t('rename tab')}
</MenuItem>
{qe &&
<CopyQueryTabUrl queryEditor={qe} />
@ -177,7 +178,7 @@ class TabbedSqlEditors extends React.PureComponent {
<MenuItem eventKey="4" onClick={this.toggleLeftBar.bind(this)}>
<i className="fa fa-cogs" />
&nbsp;
{this.state.hideLeftBar ? 'expand tool bar' : 'hide tool bar'}
{this.state.hideLeftBar ? t('expand tool bar') : t('hide tool bar')}
</MenuItem>
</DropdownButton>
</div>
@ -193,7 +194,7 @@ class TabbedSqlEditors extends React.PureComponent {
{isSelected &&
<SqlEditor
height={this.props.editorHeight}
tables={this.props.tables.filter(t => (t.queryEditorId === qe.id))}
tables={this.props.tables.filter(xt => (xt.queryEditorId === qe.id))}
queryEditor={qe}
editorQueries={this.state.queriesArray}
dataPreviewQueries={this.state.dataPreviewQueries}

View File

@ -9,6 +9,7 @@ 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,
@ -71,7 +72,7 @@ class TableElement extends React.PureComponent {
let partitionClipBoard;
if (table.partitions.partitionQuery) {
partitionQuery = table.partitions.partitionQuery;
const tt = 'Copy partition query to clipboard';
const tt = t('Copy partition query to clipboard');
partitionClipBoard = (
<CopyToClipboard
text={partitionQuery}
@ -90,7 +91,7 @@ class TableElement extends React.PureComponent {
<Well bsSize="small">
<div>
<small>
latest partition: {latest}
{t('latest partition:')} {latest}
</small> {partitionClipBoard}
</div>
</Well>
@ -106,7 +107,7 @@ class TableElement extends React.PureComponent {
<ModalTrigger
modalTitle={
<div>
Keys for table <strong>{table.name}</strong>
{t('Keys for table')} <strong>{table.name}</strong>
</div>
}
modalBody={table.indexes.map((ix, i) => (
@ -115,7 +116,7 @@ class TableElement extends React.PureComponent {
triggerNode={
<Link
className="fa fa-key pull-left m-l-2"
tooltip={`View keys & indexes (${table.indexes.length})`}
tooltip={t('View keys & indexes (%s)', table.indexes.length)}
/>
}
/>
@ -131,8 +132,8 @@ class TableElement extends React.PureComponent {
onClick={this.toggleSortColumns.bind(this)}
tooltip={
!this.state.sortColumns ?
'Sort columns alphabetically' :
'Original table column order'}
t('Sort columns alphabetically') :
t('Original table column order')}
href="#"
/>
{table.selectStar &&
@ -142,13 +143,13 @@ class TableElement extends React.PureComponent {
}
text={table.selectStar}
shouldShowText={false}
tooltipText="Copy SELECT statement to clipboard"
tooltipText={t('Copy SELECT statement to clipboard')}
/>
}
<Link
className="fa fa-times table-remove pull-left m-l-2"
onClick={this.removeTable.bind(this)}
tooltip="Remove table preview"
tooltip={t('Remove table preview')}
href="#"
/>
</ButtonGroup>

View File

@ -13,6 +13,7 @@ import { getExploreUrl } from '../../explore/exploreUtils';
import * as actions from '../actions';
import { VISUALIZE_VALIDATION_ERRORS } from '../constants';
import visTypes from '../../explore/stores/visTypes';
import { t } from '../../locales';
const CHART_TYPES = Object.keys(visTypes)
.filter(typeName => !!visTypes[typeName].showOnExplore)
@ -86,9 +87,9 @@ class VisualizeModal extends React.PureComponent {
if (!re.test(colName)) {
hints.push(
<div>
"{colName}" is not right as a column name, please alias it
(as in SELECT count(*) <strong>AS my_alias</strong>) using only
alphanumeric characters and underscores
{t('%s is not right as a column name, please alias it ' +
'(as in SELECT count(*) ', colName)} <strong>{t('AS my_alias')}</strong>) {t('using only ' +
'alphanumeric characters and underscores')}
</div>);
}
});
@ -162,7 +163,7 @@ class VisualizeModal extends React.PureComponent {
if (mainGroupBy) {
formData.groupby = [mainGroupBy.name];
}
notify.info('Creating a data source and popping a new tab');
notify.info(t('Creating a data source and popping a new tab'));
window.open(getExploreUrl(formData));
})
@ -192,7 +193,7 @@ class VisualizeModal extends React.PureComponent {
<div className="VisualizeModal">
<Modal show={this.props.show} onHide={this.props.onHide}>
<Modal.Body>
No results available for this query
{t('No results available for this query')}
</Modal.Body>
</Modal>
</div>
@ -237,17 +238,17 @@ class VisualizeModal extends React.PureComponent {
<div className="VisualizeModal">
<Modal show={this.props.show} onHide={this.props.onHide}>
<Modal.Header closeButton>
<Modal.Title>Visualize</Modal.Title>
<Modal.Title>{t('Visualize')}</Modal.Title>
</Modal.Header>
<Modal.Body>
{alerts}
{this.buildVisualizeAdvise()}
<div className="row">
<Col md={6}>
Chart Type
{t('Chart Type')}
<Select
name="select-chart-type"
placeholder="[Chart Type]"
placeholder={t('[Chart Type]')}
options={CHART_TYPES}
value={(this.state.chartType) ? this.state.chartType.value : null}
autosize={false}
@ -255,11 +256,11 @@ class VisualizeModal extends React.PureComponent {
/>
</Col>
<Col md={6}>
Datasource Name
{t('Datasource Name')}
<input
type="text"
className="form-control input-sm"
placeholder="datasource name"
placeholder={t('datasource name')}
onChange={this.changeDatasourceName.bind(this)}
value={this.state.datasourceName}
/>
@ -276,7 +277,7 @@ class VisualizeModal extends React.PureComponent {
bsStyle="primary"
disabled={(this.state.hints.length > 0)}
>
Visualize
{t('Visualize')}
</Button>
</Modal.Body>
</Modal>

View File

@ -1,3 +1,5 @@
import { t } from '../locales';
export const STATE_BSSTYLE_MAP = {
failed: 'danger',
pending: 'info',
@ -25,8 +27,8 @@ export const TIME_OPTIONS = [
];
export const VISUALIZE_VALIDATION_ERRORS = {
REQUIRE_CHART_TYPE: 'Pick a chart type!',
REQUIRE_TIME: 'To use this chart type you need at least one column flagged as a date',
REQUIRE_DIMENSION: 'To use this chart type you need at least one dimension',
REQUIRE_AGGREGATION_FUNCTION: 'To use this chart type you need at least one aggregation function',
REQUIRE_CHART_TYPE: t('Pick a chart type!'),
REQUIRE_TIME: t('To use this chart type you need at least one column flagged as a date'),
REQUIRE_DIMENSION: t('To use this chart type you need at least one dimension'),
REQUIRE_AGGREGATION_FUNCTION: t('To use this chart type you need at least one aggregation function'),
};

View File

@ -3,11 +3,12 @@ import * as actions from './actions';
import { now } from '../modules/dates';
import { addToObject, alterInObject, alterInArr, removeFromArr, getFromArr, addToArr }
from '../reduxUtils';
import { t } from '../locales';
export function getInitialState(defaultDbId) {
const defaultQueryEditor = {
id: shortid.generate(),
title: 'Untitled Query',
title: t('Untitled Query'),
sql: 'SELECT *\nFROM\nWHERE',
selectedText: null,
latestQueryId: null,
@ -40,7 +41,7 @@ export const sqlLabReducer = function (state, action) {
qe.id === state.tabHistory[state.tabHistory.length - 1]);
const qe = {
id: shortid.generate(),
title: `Copy of ${progenitor.title}`,
title: t('Copy of %s', progenitor.title),
dbId: (action.query.dbId) ? action.query.dbId : null,
schema: (action.query.schema) ? action.query.schema : null,
autorun: true,
@ -76,13 +77,13 @@ export const sqlLabReducer = function (state, action) {
[actions.MERGE_TABLE]() {
const at = Object.assign({}, action.table);
let existingTable;
state.tables.forEach((t) => {
state.tables.forEach((xt) => {
if (
t.dbId === at.dbId &&
t.queryEditorId === at.queryEditorId &&
t.schema === at.schema &&
t.name === at.name) {
existingTable = t;
xt.dbId === at.dbId &&
xt.queryEditorId === at.queryEditorId &&
xt.schema === at.schema &&
xt.name === at.name) {
existingTable = xt;
}
});
if (existingTable) {
@ -115,11 +116,11 @@ export const sqlLabReducer = function (state, action) {
delete queries[action.oldQueryId];
const newTables = [];
state.tables.forEach((t) => {
if (t.dataPreviewQueryId === action.oldQueryId) {
newTables.push(Object.assign({}, t, { dataPreviewQueryId: action.newQuery.id }));
state.tables.forEach((xt) => {
if (xt.dataPreviewQueryId === action.oldQueryId) {
newTables.push(Object.assign({}, xt, { dataPreviewQueryId: action.newQuery.id }));
} else {
newTables.push(t);
newTables.push(xt);
}
});
return Object.assign(

View File

@ -50,30 +50,30 @@ export default class AddSliceContainer extends React.PureComponent {
render() {
return (
<div className="container">
<Panel header={<h3>Create a new slice</h3>}>
<Panel header={<h3>{('Create a new slice')}</h3>}>
<Grid>
<Row>
<Col xs={12} sm={6}>
<div>
<p>Choose a datasource</p>
<p>{('Choose a datasource')}</p>
<Select
clearable={false}
name="select-datasource"
onChange={this.changeDatasource.bind(this)}
options={this.props.datasources}
placeholder="Choose a datasource"
placeholder={('Choose a datasource')}
value={this.state.datasourceValue}
/>
</div>
<br />
<div>
<p>Choose a visualization type</p>
<p>{('Choose a visualization type')}</p>
<Select
clearable={false}
name="select-vis-type"
onChange={this.changeVisType.bind(this)}
options={this.vizTypeOptions}
placeholder="Choose a visualization type"
placeholder={('Choose a visualization type')}
value={this.state.visType}
/>
</div>
@ -83,7 +83,7 @@ export default class AddSliceContainer extends React.PureComponent {
disabled={this.isBtnDisabled()}
onClick={this.gotoSlice.bind(this)}
>
Create new slice
{('Create new slice')}
</Button>
<br /><br />
</Col>

View File

@ -10,6 +10,17 @@ $(document).ready(function () {
const id = $this.attr('id');
utils.toggleCheckbox(prefix, '#' + id);
});
// for language picker dropdown
$('#language-picker a').click(function (ev) {
ev.preventDefault();
const targetUrl = ev.currentTarget.href;
$.ajax(targetUrl)
.then(() => {
location.reload();
});
});
});
export function appSetup() {

View File

@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
import { t } from '../locales';
const $ = window.$ = require('jquery');
@ -16,7 +17,7 @@ const propTypes = {
};
const defaultProps = {
placeholder: 'Select ...',
placeholder: t('Select ...'),
valueRenderer: o => (<div>{o.label}</div>),
onAsyncError: () => {},
};

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Label } from 'react-bootstrap';
import moment from 'moment';
import TooltipWrapper from './TooltipWrapper';
import { t } from '../locales';
const propTypes = {
onClick: PropTypes.func,
@ -22,14 +23,14 @@ class CacheLabel extends React.PureComponent {
updateTooltipContent() {
const cachedText = this.props.cachedTimestamp ? (
<span>
Loaded data cached <b>{moment.utc(this.props.cachedTimestamp).fromNow()}</b>
t('Loaded data cached') <b>{moment.utc(this.props.cachedTimestamp).fromNow()}</b>
</span>) :
'Loaded from cache';
t('Loaded from cache');
const tooltipContent = (
<span>
{cachedText}.
Click to force-refresh
{t('Click to force-refresh')}
</span>
);
this.setState({ tooltipContent });

View File

@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Tooltip, OverlayTrigger, MenuItem } from 'react-bootstrap';
import { t } from '../locales';
const propTypes = {
copyNode: PropTypes.node,
@ -17,7 +18,7 @@ const defaultProps = {
onCopyEnd: () => {},
shouldShowText: true,
inMenu: false,
tooltipText: 'Copy to clipboard',
tooltipText: t('Copy to clipboard'),
};
export default class CopyToClipboard extends React.Component {
@ -61,10 +62,10 @@ export default class CopyToClipboard extends React.Component {
textArea.select();
try {
if (!document.execCommand('copy')) {
throw new Error('Not successful');
throw new Error(t('Not successful'));
}
} catch (err) {
window.alert('Sorry, your browser does not support copying. Use Ctrl / Cmd + C!'); // eslint-disable-line
window.alert(t('Sorry, your browser does not support copying. Use Ctrl / Cmd + C!')); // eslint-disable-line
}
document.body.removeChild(textArea);
@ -75,7 +76,7 @@ export default class CopyToClipboard extends React.Component {
tooltipText() {
if (this.state.hasCopied) {
return 'Copied!';
return t('Copied!');
}
return this.props.tooltipText;
}

View File

@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import TooltipWrapper from './TooltipWrapper';
import { t } from '../locales';
const propTypes = {
title: PropTypes.string,
@ -8,7 +9,7 @@ const propTypes = {
onSaveTitle: PropTypes.func.isRequired,
};
const defaultProps = {
title: 'Title',
title: t('Title'),
canEdit: false,
};
@ -71,7 +72,7 @@ class EditableTitle extends React.PureComponent {
<span className="editable-title">
<TooltipWrapper
label="title"
tooltip={this.props.canEdit ? 'click to edit title' : 'You don\'t have the rights to alter this title.'}
tooltip={this.props.canEdit ? t('click to edit title') : t('You don\'t have the rights to alter this title.')}
>
<input
required

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import TooltipWrapper from './TooltipWrapper';
import { t } from '../locales';
const propTypes = {
sliceId: PropTypes.number.isRequired,
@ -28,7 +29,7 @@ export default class FaveStar extends React.Component {
return (
<TooltipWrapper
label="fave-unfave"
tooltip="Click to favorite/unfavorite"
tooltip={t('Click to favorite/unfavorite')}
>
<a
href="#"

View File

@ -8,7 +8,7 @@ import GridLayout from './components/GridLayout';
import Header from './components/Header';
import { appSetup } from '../common';
import AlertsWrapper from '../components/AlertsWrapper';
import { t } from '../locales';
import '../../stylesheets/dashboard.css';
const superset = require('../modules/superset');
@ -39,7 +39,7 @@ export function getInitialState(boostrapData) {
}
function unload() {
const message = 'You have unsaved changes.';
const message = t('You have unsaved changes.');
window.event.returnValue = message; // Gecko + IE
return message; // Gecko + Webkit, Safari, Chrome etc.
}
@ -56,9 +56,9 @@ function renderAlert() {
render(
<div className="container-fluid">
<Alert bsStyle="warning">
<strong>You have unsaved changes.</strong> Click the&nbsp;
<strong>{t('You have unsaved changes.')}</strong> {t('Click the')} &nbsp;
<i className="fa fa-save" />&nbsp;
button on the top right to save your changes.
{t('button on the top right to save your changes.')}
</Alert>
</div>,
document.getElementById('alert-container'),
@ -161,13 +161,12 @@ export function dashboardContainer(dashboard, datasources, userid) {
.addClass('danger')
.attr(
'title',
`Served from data cached ${cachedWhen}. ` +
'Click to force refresh')
t('Served from data cached %s . Click to force refresh.', cachedWhen))
.tooltip('fixTitle');
} else {
refresh
.removeClass('danger')
.attr('title', 'Click to force refresh')
.attr('title', t('Click to force refresh'))
.tooltip('fixTitle');
}
},
@ -351,8 +350,8 @@ export function dashboardContainer(dashboard, datasources, userid) {
error(error) {
const errorMsg = getAjaxErrorMsg(error);
utils.showModal({
title: 'Error',
body: 'Sorry, there was an error adding slices to this dashboard: ' + errorMsg,
title: t('Error'),
body: t('Sorry, there was an error adding slices to this dashboard: %s', errorMsg),
});
},
});

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import ModalTrigger from '../../components/ModalTrigger';
import { t } from '../../locales';
const propTypes = {
triggerNode: PropTypes.node.isRequired,
@ -31,7 +32,7 @@ export default class CodeModal extends React.PureComponent {
triggerNode={this.props.triggerNode}
isButton
beforeOpen={this.beforeOpen.bind(this)}
modalTitle="Active Dashboard Filters"
modalTitle={t('Active Dashboard Filters')}
modalBody={
<div className="CodeModal">
<pre>

View File

@ -8,6 +8,7 @@ import RefreshIntervalModal from './RefreshIntervalModal';
import SaveModal from './SaveModal';
import CodeModal from './CodeModal';
import SliceAdder from './SliceAdder';
import { t } from '../../locales';
const $ = window.$ = require('jquery');
@ -44,13 +45,13 @@ class Controls extends React.PureComponent {
}
render() {
const dashboard = this.props.dashboard;
const emailBody = `Checkout this dashboard: ${window.location.href}`;
const emailBody = t('Checkout this dashboard: %s', window.location.href);
const emailLink = 'mailto:?Subject=Superset%20Dashboard%20'
+ `${dashboard.dashboard_title}&Body=${emailBody}`;
return (
<ButtonGroup>
<Button
tooltip="Force refresh the whole dashboard"
tooltip={t('Force refresh the whole dashboard')}
onClick={this.refresh.bind(this)}
>
<i className="fa fa-refresh" />
@ -90,7 +91,7 @@ class Controls extends React.PureComponent {
onClick={() => {
window.location = `/dashboardmodelview/edit/${dashboard.id}`;
}}
tooltip="Edit this dashboard's properties"
tooltip={t('Edit this dashboard\'s properties')}
>
<i className="fa fa-edit" />
</Button>

View File

@ -7,6 +7,7 @@ import 'brace/mode/css';
import 'brace/theme/github';
import ModalTrigger from '../../components/ModalTrigger';
import { t } from '../../locales';
const propTypes = {
initialCss: PropTypes.string,
@ -61,10 +62,10 @@ class CssEditor extends React.PureComponent {
if (this.props.templates) {
return (
<div style={{ zIndex: 10 }}>
<h5>Load a template</h5>
<h5>{t('Load a template')}</h5>
<Select
options={this.props.templates}
placeholder="Load a CSS template"
placeholder={t('Load a CSS template')}
onChange={this.changeCssTemplate.bind(this)}
/>
</div>
@ -76,13 +77,13 @@ class CssEditor extends React.PureComponent {
return (
<ModalTrigger
triggerNode={this.props.triggerNode}
modalTitle="CSS"
modalTitle={t('CSS')}
isButton
modalBody={
<div>
{this.renderTemplateSelector()}
<div style={{ zIndex: 1 }}>
<h5>Live CSS Editor</h5>
<h5>{t('Live CSS Editor')}</h5>
<div style={{ border: 'solid 1px grey' }}>
<AceEditor
mode="css"

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
import ModalTrigger from '../../components/ModalTrigger';
import { t } from '../../locales';
const propTypes = {
triggerNode: PropTypes.node.isRequired,
@ -15,11 +16,11 @@ const defaultProps = {
};
const options = [
[0, "Don't refresh"],
[10, '10 seconds'],
[30, '30 seconds'],
[60, '1 minute'],
[300, '5 minutes'],
[0, t('Don\'t refresh')],
[10, t('10 seconds')],
[30, t('30 seconds')],
[60, t('1 minute')],
[300, t('5 minutes')],
].map(o => ({ value: o[0], label: o[1] }));
class RefreshIntervalModal extends React.PureComponent {
@ -34,10 +35,10 @@ class RefreshIntervalModal extends React.PureComponent {
<ModalTrigger
triggerNode={this.props.triggerNode}
isButton
modalTitle="Refresh Interval"
modalTitle={t('Refresh Interval')}
modalBody={
<div>
Choose the refresh frequency for this dashboard
{t('Choose the refresh frequency for this dashboard')}
<Select
options={options}
value={this.state.refreshFrequency}

View File

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { Button, FormControl, FormGroup, Radio } from 'react-bootstrap';
import { getAjaxErrorMsg } from '../../modules/utils';
import ModalTrigger from '../../components/ModalTrigger';
import { t } from '../../locales';
import Checkbox from '../../components/Checkbox';
const $ = window.$ = require('jquery');
@ -59,13 +60,13 @@ class SaveModal extends React.PureComponent {
if (saveType === 'newDashboard') {
window.location = `/superset/dashboard/${resp.id}/`;
} else {
notify.success('This dashboard was saved successfully.');
notify.success(t('This dashboard was saved successfully.'));
}
},
error(error) {
saveModal.close();
const errorMsg = getAjaxErrorMsg(error);
notify.error('Sorry, there was an error saving this dashboard: </ br>' + errorMsg);
notify.error(t('Sorry, there was an error saving this dashboard: ') + '</ br>' + errorMsg);
},
});
}
@ -96,8 +97,8 @@ class SaveModal extends React.PureComponent {
if (!newDashboardTitle) {
this.modal.close();
showModal({
title: 'Error',
body: 'You must pick a name for the new dashboard',
title: t('Error'),
body: t('You must pick a name for the new dashboard'),
});
} else {
data.dashboard_title = newDashboardTitle;
@ -111,7 +112,7 @@ class SaveModal extends React.PureComponent {
<ModalTrigger
ref={(modal) => { this.modal = modal; }}
triggerNode={this.props.triggerNode}
modalTitle="Save Dashboard"
modalTitle={t('Save Dashboard')}
modalBody={
<FormGroup>
<Radio
@ -119,7 +120,7 @@ class SaveModal extends React.PureComponent {
onChange={this.handleSaveTypeChange}
checked={this.state.saveType === 'overwrite'}
>
Overwrite Dashboard [{this.props.dashboard.dashboard_title}]
{t('Overwrite Dashboard [%s]', this.props.dashboard.dashboard_title)}
</Radio>
<hr />
<Radio
@ -127,11 +128,11 @@ class SaveModal extends React.PureComponent {
onChange={this.handleSaveTypeChange}
checked={this.state.saveType === 'newDashboard'}
>
Save as:
{t('Save as:')}
</Radio>
<FormControl
type="text"
placeholder="[dashboard name]"
placeholder={t('[dashboard name]')}
value={this.state.newDashName}
onFocus={this.handleNameChange}
onChange={this.handleNameChange}
@ -151,7 +152,7 @@ class SaveModal extends React.PureComponent {
bsStyle="primary"
onClick={() => { this.saveDashboard(this.state.saveType, this.state.newDashName); }}
>
Save
{t('Save')}
</Button>
</div>
}

View File

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table';
import ModalTrigger from '../../components/ModalTrigger';
import { t } from '../../locales';
require('react-bootstrap-table/css/react-bootstrap-table.css');
@ -138,13 +139,13 @@ class SliceAdder extends React.Component {
dataField="sliceName"
dataSort
>
Name
{t('Name')}
</TableHeaderColumn>
<TableHeaderColumn
dataField="vizType"
dataSort
>
Viz
{t('Viz')}
</TableHeaderColumn>
<TableHeaderColumn
dataField="modified"
@ -153,7 +154,7 @@ class SliceAdder extends React.Component {
// Will cause react-bootstrap-table to interpret the HTML returned
dataFormat={modified => modified}
>
Modified
{t('Modified')}
</TableHeaderColumn>
</BootstrapTable>
<button
@ -163,7 +164,7 @@ class SliceAdder extends React.Component {
onClick={this.addSlices}
disabled={!enableAddSlice}
>
Add Slices
{t('Add Slices')}
</button>
</div>
</div>
@ -172,12 +173,12 @@ class SliceAdder extends React.Component {
return (
<ModalTrigger
triggerNode={this.props.triggerNode}
tooltip="Add a new slice to the dashboard"
tooltip={t('Add a new slice to the dashboard')}
beforeOpen={this.onEnterModal.bind(this)}
isButton
modalBody={modalContent}
bsSize="large"
modalTitle="Add Slices to Dashboard"
modalTitle={t('Add Slices to Dashboard')}
/>
);
}

View File

@ -2,6 +2,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { t } from '../../locales';
import { getExploreUrl } from '../../explore/exploreUtils';
const propTypes = {
@ -20,14 +21,14 @@ function SliceCell({ expandedSlices, removeSlice, slice }) {
</div>
<div className="col-md-12 chart-controls">
<div id={'controls_' + slice.slice_id} className="pull-right">
<a title="Move chart" data-toggle="tooltip">
<a title={t('Move chart')} data-toggle="tooltip">
<i className="fa fa-arrows drag" />
</a>
<a className="refresh" title="Force refresh data" data-toggle="tooltip">
<a className="refresh" title={t('Force refresh data')} data-toggle="tooltip">
<i className="fa fa-repeat" />
</a>
{slice.description &&
<a title="Toggle chart description">
<a title={t('Toggle chart description')}>
<i
className="fa fa-info-circle slice_info"
title={slice.description}
@ -37,7 +38,7 @@ function SliceCell({ expandedSlices, removeSlice, slice }) {
}
<a
href={slice.edit_url}
title="Edit chart"
title={t('Edit chart')}
data-toggle="tooltip"
>
<i className="fa fa-pencil" />
@ -45,7 +46,7 @@ function SliceCell({ expandedSlices, removeSlice, slice }) {
<a
className="exportCSV"
href={getExploreUrl(slice.form_data, 'csv')}
title="Export CSV"
title={t('Export CSV')}
data-toggle="tooltip"
>
<i className="fa fa-table" />
@ -53,14 +54,14 @@ function SliceCell({ expandedSlices, removeSlice, slice }) {
<a
className="exploreChart"
href={getExploreUrl(slice.form_data)}
title="Explore chart"
title={t('Explore chart')}
data-toggle="tooltip"
>
<i className="fa fa-share" />
</a>
<a
className="remove-chart"
title="Remove chart from dashboard"
title={t('Remove chart from dashboard')}
data-toggle="tooltip"
>
<i

View File

@ -14,6 +14,7 @@ import Timer from '../../components/Timer';
import { getExploreUrl } from '../exploreUtils';
import { getFormDataFromControls } from '../stores/store';
import CachedLabel from '../../components/CachedLabel';
import { t } from '../../locales';
const CHART_STATUS_MAP = {
failed: 'danger',
@ -169,7 +170,7 @@ class ChartContainer extends React.PureComponent {
if (this.props.slice) {
title = this.props.slice.slice_name;
} else {
title = `[${this.props.table_name}] - untitled`;
title = t('%s - untitled', this.props.table_name);
}
return title;
}
@ -276,7 +277,7 @@ class ChartContainer extends React.PureComponent {
<TooltipWrapper
label="edit-desc"
tooltip="Edit slice properties"
tooltip={t('Edit slice properties')}
>
<a
className="edit-desc-icon"

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { ControlLabel, OverlayTrigger, Tooltip } from 'react-bootstrap';
import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
import { t } from '../../locales';
const propTypes = {
label: PropTypes.string.isRequired,
@ -28,7 +29,7 @@ export default class ControlHeader extends React.Component {
{this.props.description &&
<span>
<InfoTooltipWithTrigger
label="descr"
label={t('description')}
tooltip={this.props.description}
placement="top"
/>
@ -38,7 +39,7 @@ export default class ControlHeader extends React.Component {
{this.props.renderTrigger &&
<span>
<InfoTooltipWithTrigger
label="bolt"
label={t('bolt')}
tooltip={this.props.description}
placement="top"
icon="bolt"

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Alert } from 'react-bootstrap';
import { sectionsToRender, visTypes } from '../stores/visTypes';
import visTypes, { sectionsToRender } from '../stores/visTypes';
import ControlPanelSection from './ControlPanelSection';
import ControlRow from './ControlRow';
import Control from './Control';

View File

@ -6,6 +6,7 @@ import markdown from 'react-syntax-highlighter/dist/languages/markdown';
import github from 'react-syntax-highlighter/dist/styles/github';
import ModalTrigger from './../../components/ModalTrigger';
import { t } from '../../locales';
registerLanguage('markdown', markdown);
registerLanguage('html', html);
@ -57,7 +58,7 @@ export default class DisplayQueryButton extends React.PureComponent {
},
error: (data) => {
this.setState({
error: data.responseJSON ? data.responseJSON.error : 'Error...',
error: data.responseJSON ? data.responseJSON.error : t('Error...'),
isLoading: false,
});
},
@ -93,7 +94,7 @@ export default class DisplayQueryButton extends React.PureComponent {
animation={this.props.animation}
isButton
triggerNode={<span>Query</span>}
modalTitle="Query"
modalTitle={t('Query')}
bsSize="large"
beforeOpen={this.beforeOpen}
modalBody={this.renderModalBody()}

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Popover, OverlayTrigger } from 'react-bootstrap';
import CopyToClipboard from './../../components/CopyToClipboard';
import { t } from '../../locales';
const propTypes = {
slice: PropTypes.object.isRequired,
@ -63,7 +64,7 @@ export default class EmbedCodeButton extends React.Component {
<CopyToClipboard
shouldShowText={false}
text={html}
copyNode={<i className="fa fa-clipboard" title="Copy to clipboard" />}
copyNode={<i className="fa fa-clipboard" title={t('Copy to clipboard')} />}
/>
</div>
</div>
@ -72,7 +73,7 @@ export default class EmbedCodeButton extends React.Component {
<div className="col-md-6 col-sm-12">
<div className="form-group">
<small>
<label className="control-label" htmlFor="embed-height">Height</label>
<label className="control-label" htmlFor="embed-height">t('Height')</label>
</small>
<input
className="form-control input-sm"
@ -86,7 +87,7 @@ export default class EmbedCodeButton extends React.Component {
<div className="col-md-6 col-sm-12">
<div className="form-group">
<small>
<label className="control-label" htmlFor="embed-width">Width</label>
<label className="control-label" htmlFor="embed-width">t('Width')</label>
</small>
<input
className="form-control input-sm"

View File

@ -4,6 +4,7 @@ import cx from 'classnames';
import URLShortLinkButton from './URLShortLinkButton';
import EmbedCodeButton from './EmbedCodeButton';
import DisplayQueryButton from './DisplayQueryButton';
import { t } from '../../locales';
const propTypes = {
canDownload: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
@ -28,7 +29,7 @@ export default function ExploreActionButtons({
<a
href={slice.data.json_endpoint}
className="btn btn-default btn-sm"
title="Export to .json"
title={t('Export to .json')}
target="_blank"
rel="noopener noreferrer"
>
@ -38,7 +39,7 @@ export default function ExploreActionButtons({
<a
href={slice.data.csv_endpoint}
className={exportToCSVClasses}
title="Export to .csv format"
title={t('Export to .csv format')}
target="_blank"
rel="noopener noreferrer"
>

View File

@ -6,6 +6,7 @@ import { connect } from 'react-redux';
import { Modal, Alert, Button, Radio } from 'react-bootstrap';
import Select from 'react-select';
import { getExploreUrl } from '../exploreUtils';
import { t } from '../../locales';
const propTypes = {
can_overwrite: PropTypes.bool,
@ -70,7 +71,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;
@ -85,7 +86,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;
@ -93,7 +94,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;
@ -126,7 +127,7 @@ class SaveModal extends React.Component {
>
<Modal.Header closeButton>
<Modal.Title>
Save A Slice
{t('Save A Slice')}
</Modal.Title>
</Modal.Header>
<Modal.Body>
@ -147,7 +148,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 %s', this.props.slice.slice_name)}
</Radio>
}
@ -156,11 +157,11 @@ class SaveModal extends React.Component {
inline
checked={this.state.action === 'saveas'}
onChange={this.changeAction.bind(this, 'saveas')}
> Save as &nbsp;
> {t('Save as')} &nbsp;
</Radio>
<input
name="new_slice_name"
placeholder="[slice name]"
placeholder={t('[slice name]')}
onChange={this.onChange.bind(this, 'newSliceName')}
onFocus={this.changeAction.bind(this, 'saveas')}
/>
@ -173,7 +174,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')}
</Radio>
<Radio
@ -181,7 +182,7 @@ class SaveModal extends React.Component {
checked={this.state.addToDash === 'existing'}
onChange={this.changeDash.bind(this, 'existing')}
>
Add slice to existing dashboard
{t('Add slice to existing dashboard')}
</Radio>
<Select
options={this.props.dashboards}
@ -196,13 +197,14 @@ class SaveModal extends React.Component {
checked={this.state.addToDash === 'new'}
onChange={this.changeDash.bind(this, 'new')}
>
Add to new dashboard &nbsp;
{t('Add to new dashboard')} &nbsp;
</Radio>
<input
onChange={this.onChange.bind(this, 'newDashboardName')}
onFocus={this.changeDash.bind(this, 'new')}
placeholder="[dashboard name]"
placeholder={t('[dashboard name]')}
/>
</Modal.Body>
<Modal.Footer>
@ -212,7 +214,7 @@ class SaveModal extends React.Component {
className="btn pull-left"
onClick={this.saveOrOverwrite.bind(this, false)}
>
Save
{t('Save')}
</Button>
<Button
type="button"
@ -221,7 +223,7 @@ class SaveModal extends React.Component {
disabled={this.state.addToDash === 'noSave'}
onClick={this.saveOrOverwrite.bind(this, true)}
>
Save & go to dashboard
{t('Save & go to dashboard')}
</Button>
</Modal.Footer>
</Modal>

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Popover, OverlayTrigger } from 'react-bootstrap';
import CopyToClipboard from './../../components/CopyToClipboard';
import { getShortUrl } from '../../../utils/common';
import { t } from '../../locales';
const propTypes = {
slice: PropTypes.object.isRequired,
@ -28,12 +29,12 @@ export default class URLShortLinkButton extends React.Component {
}
renderPopover() {
const emailBody = `Check out this slice: ${this.state.shortUrl}`;
const emailBody = t('Check out this slice: %s', this.state.shortUrl);
return (
<Popover id="shorturl-popover">
<CopyToClipboard
text={this.state.shortUrl}
copyNode={<i className="fa fa-clipboard" title="Copy to clipboard" />}
copyNode={<i className="fa fa-clipboard" title={t('Copy to clipboard')} />}
/>
&nbsp;&nbsp;
<a href={`mailto:?Subject=Superset%20Slice%20&Body=${emailBody}`}>

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Col, Row, FormGroup, FormControl } from 'react-bootstrap';
import ControlHeader from '../ControlHeader';
import { t } from '../../../locales';
const propTypes = {
name: PropTypes.string.isRequired,
@ -51,10 +52,10 @@ export default class BoundsControl extends React.Component {
const mm = this.state.minMax;
const errors = [];
if (mm[0] && isNaN(mm[0])) {
errors.push('`Min` value should be numeric or empty');
errors.push(t('`Min` value should be numeric or empty'));
}
if (mm[1] && isNaN(mm[1])) {
errors.push('`Max` value should be numeric or empty');
errors.push(t('`Max` value should be numeric or empty'));
}
if (errors.length === 0) {
this.props.onChange([parseFloat(mm[0]), parseFloat(mm[1])], errors);
@ -71,7 +72,7 @@ export default class BoundsControl extends React.Component {
<Col xs={6}>
<FormControl
type="text"
placeholder="Min"
placeholder={t('Min')}
onChange={this.onMinChange}
value={this.state.minMax[0]}
/>
@ -79,7 +80,7 @@ export default class BoundsControl extends React.Component {
<Col xs={6}>
<FormControl
type="text"
placeholder="Max"
placeholder={t('Max')}
onChange={this.onMaxChange}
value={this.state.minMax[1]}
/>

View File

@ -5,6 +5,7 @@ import { Table } from 'reactable';
import { Label, FormControl, Modal, OverlayTrigger, Tooltip } from 'react-bootstrap';
import ControlHeader from '../ControlHeader';
import { t } from '../../../locales';
const propTypes = {
description: PropTypes.string,
@ -66,7 +67,7 @@ export default class DatasourceControl extends React.PureComponent {
},
error() {
that.setState({ loading: false });
notify.error('Something went wrong while fetching the datasource list');
notify.error(t('Something went wrong while fetching the datasource list'));
},
});
}
@ -91,7 +92,7 @@ export default class DatasourceControl extends React.PureComponent {
<OverlayTrigger
placement="right"
overlay={
<Tooltip id={'error-tooltip'}>Click to point to another datasource</Tooltip>
<Tooltip id={'error-tooltip'}>{t('Click to point to another datasource')}</Tooltip>
}
>
<Label onClick={this.toggleModal} style={{ cursor: 'pointer' }} className="m-r-5">
@ -102,7 +103,7 @@ export default class DatasourceControl extends React.PureComponent {
placement="right"
overlay={
<Tooltip id={'edit-datasource-tooltip'}>
Edit the datasource's configuration
{t('Edit the datasource\'s configuration')}
</Tooltip>
}
>
@ -118,7 +119,7 @@ export default class DatasourceControl extends React.PureComponent {
bsSize="lg"
>
<Modal.Header closeButton>
<Modal.Title>Select a datasource</Modal.Title>
<Modal.Title>{t('Select a datasource')}</Modal.Title>
</Modal.Header>
<Modal.Body>
<div>
@ -128,7 +129,7 @@ export default class DatasourceControl extends React.PureComponent {
type="text"
bsSize="sm"
value={this.state.filter}
placeholder="Search / Filter"
placeholder={t('Search / Filter')}
onChange={this.changeSearch}
/>
</div>

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import Select from 'react-select';
import { Button, Row, Col } from 'react-bootstrap';
import SelectControl from './SelectControl';
import { t } from '../../../locales';
const operatorsArr = [
{ val: 'in', type: 'array', useSelect: true, multi: true },
@ -110,7 +111,7 @@ export default class Filter extends React.Component {
onChange={this.changeText.bind(this)}
value={filter.val}
className="form-control input-sm"
placeholder="Filter value"
placeholder={t('Filter value')}
/>
);
}
@ -139,7 +140,7 @@ export default class Filter extends React.Component {
<Col md={12}>
<Select
id="select-col"
placeholder={this.props.having ? 'Select metric' : 'Select column'}
placeholder={this.props.having ? t('Select metric') : t('Select column')}
clearable={false}
options={colChoices}
value={filter.col}
@ -151,7 +152,7 @@ export default class Filter extends React.Component {
<Col md={3}>
<Select
id="select-op"
placeholder="Select operator"
placeholder={t('Select operator')}
options={opsChoices}
clearable={false}
value={filter.op}

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Button, Row, Col } from 'react-bootstrap';
import Filter from './Filter';
import { t } from '../../../locales';
const $ = window.$ = require('jquery');
@ -134,7 +135,7 @@ export default class FilterControl extends React.Component {
bsSize="sm"
onClick={this.addFilter.bind(this)}
>
<i className="fa fa-plus" /> &nbsp; Add Filter
<i className="fa fa-plus" /> &nbsp; {t('Add Filter')}
</Button>
</Col>
</Row>

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import Select, { Creatable } from 'react-select';
import ControlHeader from '../ControlHeader';
import { t } from '../../../locales';
const propTypes = {
choices: PropTypes.array,
@ -102,7 +103,7 @@ export default class SelectControl extends React.PureComponent {
const selectProps = {
multi: this.props.multi,
name: `select-${this.props.name}`,
placeholder: `Select (${this.state.options.length})`,
placeholder: t('Select %s', this.state.options.length),
options: this.state.options,
value: this.props.value,
valueKey: this.props.valueKey,

View File

@ -12,6 +12,7 @@ import 'brace/theme/textmate';
import ControlHeader from '../ControlHeader';
import ModalTrigger from '../../../components/ModalTrigger';
import { t } from '../../../locales';
const propTypes = {
name: PropTypes.string.isRequired,
@ -59,7 +60,7 @@ export default class TextAreaControl extends React.Component {
<FormGroup controlId="formControlsTextarea">
<FormControl
componentClass="textarea"
placeholder="textarea"
placeholder={t('textarea')}
onChange={this.onControlChange.bind(this)}
value={this.props.value}
style={{ height: this.props.height }}
@ -77,7 +78,7 @@ export default class TextAreaControl extends React.Component {
modalTitle={controlHeader}
triggerNode={
<Button bsSize="small" className="m-t-5">
Edit <strong>{this.props.language}</strong> in modal
{t('Edit')} <strong>{this.props.language}</strong> {t('in modal')}
</Button>
}
modalBody={this.renderEditor(true)}

View File

@ -5,6 +5,7 @@ import {
Tooltip } from 'react-bootstrap';
import visTypes from '../../stores/visTypes';
import ControlHeader from '../ControlHeader';
import { t } from '../../../locales';
const propTypes = {
description: PropTypes.string,
@ -106,7 +107,7 @@ export default class VizTypeControl extends React.PureComponent {
bsSize="lg"
>
<Modal.Header closeButton>
<Modal.Title>Select a visualization type</Modal.Title>
<Modal.Title>{t('Select a visualization type')}</Modal.Title>
</Modal.Header>
<Modal.Body>
<div>
@ -116,7 +117,7 @@ export default class VizTypeControl extends React.PureComponent {
type="text"
bsSize="sm"
value={this.state.filter}
placeholder="Search / Filter"
placeholder={t('Search / Filter')}
onChange={this.changeSearch}
/>
</div>

View File

@ -24,7 +24,8 @@ const exploreViewContainer = document.getElementById('app');
const bootstrapData = JSON.parse(exploreViewContainer.getAttribute('data-bootstrap'));
const controls = getControlsState(bootstrapData, bootstrapData.form_data);
delete bootstrapData.form_data;
delete bootstrapData.common.locale;
delete bootstrapData.common.language_pack;
// Initial state
const bootstrappedState = Object.assign(
@ -56,6 +57,7 @@ const initState = {
const store = createStore(rootReducer, initState,
compose(applyMiddleware(thunk), initEnhancer(false)),
);
ReactDOM.render(
<Provider store={store}>
<div>

View File

@ -1,6 +1,7 @@
/* eslint camelcase: 0 */
import { now } from '../../modules/dates';
import * as actions from '../actions/chartActions';
import { t } from '../../locales';
export default function chartReducer(state = {}, action) {
const actionHandlers = {
@ -28,13 +29,13 @@ export default function chartReducer(state = {}, action) {
return Object.assign({}, state,
{
chartStatus: 'stopped',
chartAlert: 'Updating chart was stopped',
chartAlert: t('Updating chart was stopped'),
});
},
[actions.CHART_RENDERING_FAILED]() {
return Object.assign({}, state, {
chartStatus: 'failed',
chartAlert: 'An error occurred while rendering the visualization: ' + action.error,
chartAlert: t('An error occurred while rendering the visualization: %s', action.error),
});
},
[actions.CHART_UPDATE_TIMEOUT]() {
@ -43,16 +44,16 @@ export default function chartReducer(state = {}, action) {
chartAlert: (
'<strong>Query timeout</strong> - visualization query are set to timeout at ' +
`${action.timeout} seconds. ` +
'Perhaps your data has grown, your database is under unusual load, ' +
t('Perhaps your data has grown, your database is under unusual load, ' +
'or you are simply querying a data source that is to large ' +
'to be processed within the timeout range. ' +
'If that is the case, we recommend that you summarize your data further.'),
'If that is the case, we recommend that you summarize your data further.')),
});
},
[actions.CHART_UPDATE_FAILED]() {
return Object.assign({}, state, {
chartStatus: 'failed',
chartAlert: action.queryResponse ? action.queryResponse.error : 'Network error.',
chartAlert: action.queryResponse ? action.queryResponse.error : t('Network error.'),
chartUpdateEndTime: now(),
queryResponse: action.queryResponse,
});

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,19 @@
import { D3_TIME_FORMAT_OPTIONS } from './controls';
import * as v from '../validators';
import { t } from '../../locales';
export const sections = {
druidTimeSeries: {
label: 'Time',
label: t('Time'),
expanded: true,
description: 'Time related form attributes',
description: t('Time related form attributes'),
controlSetRows: [
['granularity', 'druid_time_origin'],
['since', 'until'],
],
},
datasourceAndVizType: {
label: 'Datasource & Chart Type',
label: t('Datasource & Chart Type'),
expanded: true,
controlSetRows: [
['datasource'],
@ -21,14 +22,14 @@ export const sections = {
],
},
colorScheme: {
label: 'Color Scheme',
label: t('Color Scheme'),
controlSetRows: [
['color_scheme'],
],
},
sqlaTimeSeries: {
label: 'Time',
description: 'Time related form attributes',
label: t('Time'),
description: t('Time related form attributes'),
expanded: true,
controlSetRows: [
['granularity_sqla', 'time_grain_sqla'],
@ -36,16 +37,16 @@ export const sections = {
],
},
sqlClause: {
label: 'SQL',
label: t('SQL'),
controlSetRows: [
['where'],
['having'],
],
description: 'This section exposes ways to include snippets of SQL in your query',
description: t('This section exposes ways to include snippets of SQL in your query'),
},
NVD3TimeSeries: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['metrics'],
@ -55,10 +56,10 @@ export const sections = {
],
},
{
label: 'Advanced Analytics',
description: 'This section contains options ' +
label: t('Advanced Analytics'),
description: t('This section contains options ' +
'that allow for advanced analytical post processing ' +
'of query results',
'of query results'),
controlSetRows: [
['rolling_type', 'rolling_periods', 'min_periods'],
['time_compare', null],
@ -69,15 +70,15 @@ export const sections = {
],
filters: [
{
label: 'Filters',
label: t('Filters'),
expanded: true,
controlSetRows: [['filters']],
},
{
label: 'Result Filters',
label: t('Result Filters'),
expanded: true,
description: 'The filters to apply after post-aggregation.' +
'Leave the value control empty to filter empty strings or nulls',
description: t('The filters to apply after post-aggregation.' +
'Leave the value control empty to filter empty strings or nulls'),
controlSetRows: [['having_filters']],
},
],
@ -85,11 +86,11 @@ export const sections = {
export const visTypes = {
dist_bar: {
label: 'Distribution - Bar Chart',
label: t('Distribution - Bar Chart'),
showOnExplore: true,
controlPanelSections: [
{
label: 'Query',
label: t('Chart Options'),
controlSetRows: [
['metrics'],
['groupby'],
@ -98,7 +99,7 @@ export const visTypes = {
],
},
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['color_scheme'],
['show_legend', 'show_bar_value'],
@ -112,21 +113,21 @@ export const visTypes = {
],
controlOverrides: {
groupby: {
label: 'Series',
label: t('Series'),
},
columns: {
label: 'Breakdowns',
description: 'Defines how each series is broken down',
label: t('Breakdowns'),
description: t('Defines how each series is broken down'),
},
},
},
pie: {
label: 'Pie Chart',
label: t('Pie Chart'),
showOnExplore: true,
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['metrics', 'groupby'],
@ -134,7 +135,7 @@ export const visTypes = {
],
},
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['pie_label_type'],
['donut', 'show_legend'],
@ -146,13 +147,13 @@ export const visTypes = {
},
line: {
label: 'Time Series - Line Chart',
label: t('Time Series - Line Chart'),
showOnExplore: true,
requiresTime: true,
controlPanelSections: [
sections.NVD3TimeSeries[0],
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['color_scheme'],
['show_brush', 'show_legend'],
@ -161,14 +162,14 @@ export const visTypes = {
],
},
{
label: 'X Axis',
label: t('X Axis'),
controlSetRows: [
['x_axis_label', 'bottom_margin'],
['x_axis_showminmax', 'x_axis_format'],
],
},
{
label: 'Y Axis',
label: t('Y Axis'),
controlSetRows: [
['y_axis_label', 'left_margin'],
['y_axis_showminmax', 'y_log_scale'],
@ -186,24 +187,24 @@ export const visTypes = {
},
dual_line: {
label: 'Dual Axis Line Chart',
label: t('Dual Axis Line Chart'),
requiresTime: true,
controlPanelSections: [
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['color_scheme'],
['x_axis_format'],
],
},
{
label: 'Y Axis 1',
label: t('Y Axis 1'),
controlSetRows: [
['metric', 'y_axis_format'],
],
},
{
label: 'Y Axis 2',
label: t('Y Axis 2'),
controlSetRows: [
['metric_2', 'y_axis_2_format'],
],
@ -211,11 +212,11 @@ export const visTypes = {
],
controlOverrides: {
metric: {
label: 'Left Axis Metric',
description: 'Choose a metric for left axis',
label: t('Left Axis Metric'),
description: t('Choose a metric for left axis'),
},
y_axis_format: {
label: 'Left Axis Format',
label: t('Left Axis Format'),
},
x_axis_format: {
choices: D3_TIME_FORMAT_OPTIONS,
@ -225,13 +226,13 @@ export const visTypes = {
},
bar: {
label: 'Time Series - Bar Chart',
label: t('Time Series - Bar Chart'),
showOnExplore: true,
requiresTime: true,
controlPanelSections: [
sections.NVD3TimeSeries[0],
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['color_scheme'],
['show_brush', 'show_legend', 'show_bar_value'],
@ -241,7 +242,7 @@ export const visTypes = {
],
},
{
label: 'Axes',
label: t('Axes'),
controlSetRows: [
['x_axis_format', 'y_axis_format'],
['x_axis_showminmax', 'reduce_x_ticks'],
@ -260,12 +261,12 @@ export const visTypes = {
},
compare: {
label: 'Time Series - Percent Change',
label: t('Time Series - Percent Change'),
requiresTime: true,
controlPanelSections: [
sections.NVD3TimeSeries[0],
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['color_scheme'],
['x_axis_format', 'y_axis_format'],
@ -282,12 +283,12 @@ export const visTypes = {
},
area: {
label: 'Time Series - Stacked',
label: t('Time Series - Stacked'),
requiresTime: true,
controlPanelSections: [
sections.NVD3TimeSeries[0],
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['show_brush', 'show_legend'],
['line_interpolation', 'stacked_style'],
@ -297,7 +298,7 @@ export const visTypes = {
],
},
{
label: 'Axes',
label: t('Axes'),
controlSetRows: [
['x_axis_format', 'x_axis_showminmax'],
['y_axis_format', 'y_axis_bounds'],
@ -318,11 +319,11 @@ export const visTypes = {
},
table: {
label: 'Table View',
label: t('Table View'),
controlPanelSections: [
{
label: 'GROUP BY',
description: 'Use this section if you want a query that aggregates',
label: t('GROUP BY'),
description: t('Use this section if you want a query that aggregates'),
controlSetRows: [
['groupby', 'metrics'],
['include_time', null],
@ -330,15 +331,15 @@ export const visTypes = {
],
},
{
label: 'NOT GROUPED BY',
description: 'Use this section if you want to query atomic rows',
label: t('NOT GROUPED BY'),
description: t('Use this section if you want to query atomic rows'),
controlSetRows: [
['all_columns'],
['order_by_cols'],
],
},
{
label: 'Options',
label: t('Options'),
controlSetRows: [
['table_timestamp_format'],
['row_limit', 'page_length'],
@ -357,10 +358,10 @@ export const visTypes = {
},
markup: {
label: 'Markup',
label: t('Markup'),
controlPanelSections: [
{
label: 'Code',
label: t('Code'),
controlSetRows: [
['markup_type'],
['code'],
@ -370,10 +371,10 @@ export const visTypes = {
},
pivot_table: {
label: 'Pivot Table',
label: t('Pivot Table'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['groupby', 'columns'],
@ -395,10 +396,10 @@ export const visTypes = {
},
separator: {
label: 'Separator',
label: t('Separator'),
controlPanelSections: [
{
label: 'Code',
label: t('Code'),
controlSetRows: [
['markup_type'],
['code'],
@ -417,10 +418,10 @@ export const visTypes = {
},
word_cloud: {
label: 'Word Cloud',
label: t('Word Cloud'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['series', 'metric', 'limit'],
@ -438,10 +439,10 @@ export const visTypes = {
},
treemap: {
label: 'Treemap',
label: t('Treemap'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['metrics'],
@ -449,7 +450,7 @@ export const visTypes = {
],
},
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['color_scheme'],
['treemap_ratio'],
@ -465,11 +466,11 @@ export const visTypes = {
},
cal_heatmap: {
label: 'Calendar Heatmap',
label: t('Calendar Heatmap'),
requiresTime: true,
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['metric'],
@ -486,10 +487,10 @@ export const visTypes = {
},
box_plot: {
label: 'Box Plot',
label: t('Box Plot'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['metrics'],
@ -497,7 +498,7 @@ export const visTypes = {
],
},
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['color_scheme'],
['whisker_options'],
@ -507,10 +508,10 @@ export const visTypes = {
},
bubble: {
label: 'Bubble Chart',
label: t('Bubble Chart'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['series', 'entity'],
@ -518,20 +519,20 @@ export const visTypes = {
],
},
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['color_scheme'],
['show_legend', null],
],
},
{
label: 'Bubbles',
label: t('Bubbles'),
controlSetRows: [
['size', 'max_bubble_size'],
],
},
{
label: 'X Axis',
label: t('X Axis'),
controlSetRows: [
['x_axis_label', 'left_margin'],
['x', 'x_axis_format'],
@ -539,7 +540,7 @@ export const visTypes = {
],
},
{
label: 'Y Axis',
label: t('Y Axis'),
controlSetRows: [
['y_axis_label', 'bottom_margin'],
['y', 'y_axis_format'],
@ -558,11 +559,11 @@ export const visTypes = {
},
bullet: {
label: 'Bullet Chart',
label: t('Bullet Chart'),
requiresTime: false,
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['metric'],
@ -581,10 +582,10 @@ export const visTypes = {
},
big_number: {
label: 'Big Number with Trendline',
label: t('Big Number with Trendline'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['metric'],
@ -600,16 +601,16 @@ export const visTypes = {
],
controlOverrides: {
y_axis_format: {
label: 'Number format',
label: t('Number format'),
},
},
},
big_number_total: {
label: 'Big Number',
label: t('Big Number'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['metric'],
@ -625,16 +626,16 @@ export const visTypes = {
],
controlOverrides: {
y_axis_format: {
label: 'Number format',
label: t('Number format'),
},
},
},
histogram: {
label: 'Histogram',
label: t('Histogram'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['all_columns_x'],
@ -642,7 +643,7 @@ export const visTypes = {
],
},
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['color_scheme'],
['link_length'],
@ -651,22 +652,22 @@ export const visTypes = {
],
controlOverrides: {
all_columns_x: {
label: 'Numeric Column',
description: 'Select the numeric column to draw the histogram',
label: t('Numeric Column'),
description: t('Select the numeric column to draw the histogram'),
},
link_length: {
label: 'No of Bins',
description: 'Select number of bins for the histogram',
label: t('No of Bins'),
description: t('Select number of bins for the histogram'),
default: 5,
},
},
},
sunburst: {
label: 'Sunburst',
label: t('Sunburst'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['groupby'],
@ -675,7 +676,7 @@ export const visTypes = {
],
},
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['color_scheme'],
],
@ -683,27 +684,27 @@ export const visTypes = {
],
controlOverrides: {
metric: {
label: 'Primary Metric',
description: 'The primary metric is used to define the arc segment sizes',
label: t('Primary Metric'),
description: t('The primary metric is used to define the arc segment sizes'),
},
secondary_metric: {
label: 'Secondary Metric',
description: 'This secondary metric is used to ' +
label: t('Secondary Metric'),
description: t('This secondary metric is used to ' +
'define the color as a ratio against the primary metric. ' +
'If the two metrics match, color is mapped level groups',
'If the two metrics match, color is mapped level groups'),
},
groupby: {
label: 'Hierarchy',
description: 'This defines the level of the hierarchy',
label: t('Hierarchy'),
description: t('This defines the level of the hierarchy'),
},
},
},
sankey: {
label: 'Sankey',
label: t('Sankey'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['groupby'],
@ -712,7 +713,7 @@ export const visTypes = {
],
},
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['color_scheme'],
],
@ -720,17 +721,17 @@ export const visTypes = {
],
controlOverrides: {
groupby: {
label: 'Source / Target',
description: 'Choose a source and a target',
label: t('Source / Target'),
description: t('Choose a source and a target'),
},
},
},
directed_force: {
label: 'Directed Force Layout',
label: t('Directed Force Layout'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['groupby'],
@ -739,7 +740,7 @@ export const visTypes = {
],
},
{
label: 'Options',
label: t('Options'),
controlSetRows: [
['link_length'],
['charge'],
@ -748,16 +749,16 @@ export const visTypes = {
],
controlOverrides: {
groupby: {
label: 'Source / Target',
description: 'Choose a source and a target',
label: t('Source / Target'),
description: t('Choose a source and a target'),
},
},
},
chord: {
label: 'Chord Diagram',
label: t('Chord Diagram'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['groupby', 'columns'],
@ -765,7 +766,7 @@ export const visTypes = {
],
},
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['y_axis_format', null],
['color_scheme'],
@ -774,28 +775,28 @@ export const visTypes = {
],
controlOverrides: {
y_axis_format: {
label: 'Number format',
description: 'Choose a number format',
label: t('Number format'),
description: t('Choose a number format'),
},
groupby: {
label: 'Source',
label: t('Source'),
multi: false,
validators: [v.nonEmpty],
description: 'Choose a source',
description: t('Choose a source'),
},
columns: {
label: 'Target',
label: t('Target'),
multi: false,
validators: [v.nonEmpty],
description: 'Choose a target',
description: t('Choose a target'),
},
},
},
country_map: {
label: 'Country Map',
label: t('Country Map'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['entity'],
@ -803,7 +804,7 @@ export const visTypes = {
],
},
{
label: 'Options',
label: t('Options'),
controlSetRows: [
['select_country'],
['linear_color_scheme'],
@ -812,11 +813,11 @@ export const visTypes = {
],
controlOverrides: {
entity: {
label: 'ISO 3166-1 codes of region/province/department',
description: "It's ISO 3166-1 of your region/province/department in your table. (see documentation for list of ISO 3166-1)",
label: t('ISO 3166-1 codes of region/province/department'),
description: t('It\'s ISO 3166-1 of your region/province/department in your table. (see documentation for list of ISO 3166-1)'),
},
metric: {
label: 'Metric',
label: t('Metric'),
description: 'Metric to display bottom title',
},
linear_color_scheme: {
@ -825,10 +826,10 @@ export const visTypes = {
},
},
world_map: {
label: 'World Map',
label: t('World Map'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['entity'],
@ -837,7 +838,7 @@ export const visTypes = {
],
},
{
label: 'Bubbles',
label: t('Bubbles'),
controlSetRows: [
['show_bubbles'],
['secondary_metric'],
@ -847,25 +848,25 @@ export const visTypes = {
],
controlOverrides: {
entity: {
label: 'Country Control',
description: '3 letter code of the country',
label: t('Country Control'),
description: t('3 letter code of the country'),
},
metric: {
label: 'Metric for color',
description: 'Metric that defines the color of the country',
label: t('Metric for color'),
description: t('Metric that defines the color of the country'),
},
secondary_metric: {
label: 'Bubble size',
description: 'Metric that defines the size of the bubble',
label: t('Bubble size'),
description: t('Metric that defines the size of the bubble'),
},
},
},
filter_box: {
label: 'Filter Box',
label: t('Filter Box'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['groupby'],
@ -881,11 +882,10 @@ export const visTypes = {
],
controlOverrides: {
groupby: {
label: 'Filter controls',
description: (
label: t('Filter controls'),
description: t(
'The controls you want to filter on. Note that only columns ' +
'checked as "filterable" will show up on this list.'
),
'checked as "filterable" will show up on this list.'),
mapStateToProps: state => ({
options: (state.datasource) ? state.datasource.columns.filter(c => c.filterable) : [],
}),
@ -894,10 +894,10 @@ export const visTypes = {
},
iframe: {
label: 'iFrame',
label: t('iFrame'),
controlPanelSections: [
{
label: 'Options',
label: t('Options'),
controlSetRows: [
['url'],
],
@ -906,10 +906,10 @@ export const visTypes = {
},
para: {
label: 'Parallel Coordinates',
label: t('Parallel Coordinates'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['series'],
@ -928,10 +928,10 @@ export const visTypes = {
},
heatmap: {
label: 'Heatmap',
label: t('Heatmap'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['all_columns_x', 'all_columns_y'],
@ -939,7 +939,7 @@ export const visTypes = {
],
},
{
label: 'Heatmap Options',
label: t('Heatmap Options'),
controlSetRows: [
['linear_color_scheme'],
['xscale_interval', 'yscale_interval'],
@ -974,11 +974,11 @@ export const visTypes = {
},
horizon: {
label: 'Horizon',
label: t('Horizon'),
controlPanelSections: [
sections.NVD3TimeSeries[0],
{
label: 'Chart Options',
label: t('Chart Options'),
controlSetRows: [
['series_height', 'horizon_color_scale'],
],
@ -987,10 +987,10 @@ export const visTypes = {
},
mapbox: {
label: 'Mapbox',
label: t('Mapbox'),
controlPanelSections: [
{
label: 'Query',
label: t('Query'),
expanded: true,
controlSetRows: [
['all_columns_x', 'all_columns_y'],
@ -1000,21 +1000,21 @@ export const visTypes = {
],
},
{
label: 'Points',
label: t('Points'),
controlSetRows: [
['point_radius'],
['point_radius_unit'],
],
},
{
label: 'Labelling',
label: t('Labelling'),
controlSetRows: [
['mapbox_label'],
['pandas_aggfunc'],
],
},
{
label: 'Visual Tweaks',
label: t('Visual Tweaks'),
controlSetRows: [
['render_while_dragging'],
['mapbox_style'],
@ -1023,7 +1023,7 @@ export const visTypes = {
],
},
{
label: 'Viewport',
label: t('Viewport'),
controlSetRows: [
['viewport_longitude'],
['viewport_latitude'],
@ -1033,36 +1033,36 @@ export const visTypes = {
],
controlOverrides: {
all_columns_x: {
label: 'Longitude',
description: 'Column containing longitude data',
label: t('Longitude'),
description: t('Column containing longitude data'),
},
all_columns_y: {
label: 'Latitude',
description: 'Column containing latitude data',
label: t('Latitude'),
description: t('Column containing latitude data'),
},
pandas_aggfunc: {
label: 'Cluster label aggregator',
description: 'Aggregate function applied to the list of points ' +
'in each cluster to produce the cluster label.',
label: t('Cluster label aggregator'),
description: t('Aggregate function applied to the list of points ' +
'in each cluster to produce the cluster label.'),
},
rich_tooltip: {
label: 'Tooltip',
description: 'Show a tooltip when hovering over points and clusters ' +
'describing the label',
label: t('Tooltip'),
description: t('Show a tooltip when hovering over points and clusters ' +
'describing the label'),
},
groupby: {
description: 'One or many controls to group by. If grouping, latitude ' +
'and longitude columns must be present.',
description: t('One or many controls to group by. If grouping, latitude ' +
'and longitude columns must be present.'),
},
},
},
event_flow: {
label: 'Event flow',
label: t('Event flow'),
requiresTime: true,
controlPanelSections: [
{
label: 'Event definition',
label: t('Event definition'),
controlSetRows: [
['entity'],
['all_columns_x'],
@ -1072,7 +1072,7 @@ export const visTypes = {
],
},
{
label: 'Additional meta data',
label: t('Additional meta data'),
controlSetRows: [
['all_columns'],
],
@ -1080,11 +1080,11 @@ export const visTypes = {
],
controlOverrides: {
entity: {
label: 'Column containing entity ids',
description: 'e.g., a "user id" column',
label: t('Column containing entity ids'),
description: t('e.g., a "user id" column'),
},
all_columns_x: {
label: 'Column containing event names',
label: t('Column containing event names'),
validators: [v.nonEmpty],
default: control => (
control.choices && control.choices.length > 0 ?
@ -1092,12 +1092,12 @@ export const visTypes = {
),
},
row_limit: {
label: 'Event count limit',
description: 'The maximum number of events to return, equivalent to number of rows',
label: t('Event count limit'),
description: t('The maximum number of events to return, equivalent to number of rows'),
},
all_columns: {
label: 'Meta data',
description: 'Select any columns for meta data inspection',
label: t('Meta data'),
description: t('Select any columns for meta data inspection'),
},
},
},

View File

@ -0,0 +1,32 @@
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;

View File

@ -0,0 +1,148 @@
/* 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;

View File

@ -4,6 +4,7 @@ import Mustache from 'mustache';
import vizMap from '../../visualizations/main';
import { getExploreUrl } from '../explore/exploreUtils';
import { applyDefaultFormData } from '../explore/stores/store';
import { t } from '../locales';
const utils = require('./utils');
@ -29,7 +30,7 @@ const px = function (state) {
}
}
$('.favstar')
.attr('title', 'Click to favorite/unfavorite')
.attr('title', t('Click to favorite/unfavorite'))
.css('cursor', 'pointer')
.each(show)
.each(function () {
@ -126,10 +127,10 @@ const px = function (state) {
if (status === 0) {
// This may happen when the worker in gunicorn times out
msg += (
'The server could not be reached. You may want to ' +
'verify your connection and try again.');
t('The server could not be reached. You may want to ' +
'verify your connection and try again.'));
} else {
msg += 'An unknown error occurred. (Status: ' + status + ')';
msg += (t('An unknown error occurred. (Status: %s )', status));
}
}
return msg;
@ -219,7 +220,7 @@ const px = function (state) {
vizMap[formData.viz_type](this, queryResponse);
this.done(queryResponse);
} catch (e) {
this.error('An error occurred while rendering the visualization: ' + e);
this.error(t('An error occurred while rendering the visualization: %s', e));
}
},
error: (err) => {

View File

@ -6,6 +6,7 @@ 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,
@ -20,25 +21,25 @@ export default function App(props) {
</Col>
<Col md={9}>
<Tabs id="options">
<Tab eventKey={1} title={<div><i className="fa fa-star" /> Favorites</div>}>
<Tab eventKey={1} title={<div><i className="fa fa-star" /> {t('Favorites')}</div>}>
<Panel><Favorites user={props.user} /></Panel>
</Tab>
<Tab
eventKey={2}
title={
<div><i className="fa fa-paint-brush" /> Created Content</div>
<div><i className="fa fa-paint-brush" /> {t('Created Content')}</div>
}
>
<Panel>
<CreatedContent user={props.user} />
</Panel>
</Tab>
<Tab eventKey={3} title={<div><i className="fa fa-list" /> Recent Activity</div>}>
<Tab eventKey={3} title={<div><i className="fa fa-list" /> {t('Recent Activity')}</div>}>
<Panel>
<RecentActivity user={props.user} />
</Panel>
</Tab>
<Tab eventKey={4} title={<div><i className="fa fa-lock" /> Security & Access</div>}>
<Tab eventKey={4} title={<div><i className="fa fa-lock" /> {t('Security & Access')}</div>}>
<Panel>
<Security user={props.user} />
</Panel>

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import TableLoader from './TableLoader';
import { t } from '../../locales';
const propTypes = {
user: PropTypes.object.isRequired,
@ -29,7 +30,7 @@ class CreatedContent extends React.PureComponent {
className="table table-condensed"
columns={['slice', 'favorited']}
mutator={mutator}
noDataText="No slices"
noDataText={t('No slices')}
sortable
/>
);
@ -45,7 +46,7 @@ class CreatedContent extends React.PureComponent {
className="table table-condensed"
mutator={mutator}
dataEndpoint={`/superset/created_dashboards/${this.props.user.userId}/`}
noDataText="No dashboards"
noDataText={t('No dashboards')}
columns={['dashboard', 'favorited']}
sortable
/>
@ -54,10 +55,10 @@ class CreatedContent extends React.PureComponent {
render() {
return (
<div>
<h3>Dashboards</h3>
<h3>{t('Dashboards')}</h3>
{this.renderDashboardTable()}
<hr />
<h3>Slices</h3>
<h3>{t('Slices')}</h3>
{this.renderSliceTable()}
</div>
);

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import TableLoader from './TableLoader';
import { t } from '../../locales';
const propTypes = {
user: PropTypes.object.isRequired,
@ -30,7 +31,7 @@ export default class Favorites extends React.PureComponent {
className="table table-condensed"
columns={['slice', 'creator', 'favorited']}
mutator={mutator}
noDataText="No favorite slices yet, go click on stars!"
noDataText={t('No favorite slices yet, go click on stars!')}
sortable
/>
);
@ -46,7 +47,7 @@ export default class Favorites extends React.PureComponent {
className="table table-condensed"
mutator={mutator}
dataEndpoint={`/superset/fave_dashboards/${this.props.user.userId}/`}
noDataText="No favorite dashboards yet, go click on stars!"
noDataText={t('No favorite dashboards yet, go click on stars!')}
columns={['dashboard', 'creator', 'favorited']}
sortable
/>
@ -55,10 +56,10 @@ export default class Favorites extends React.PureComponent {
render() {
return (
<div>
<h3>Dashboards</h3>
<h3>{t('Dashboards')}</h3>
{this.renderDashboardTable()}
<hr />
<h3>Slices</h3>
<h3>{t('Slices')}</h3>
{this.renderSliceTable()}
</div>
);

View File

@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Badge, Label } from 'react-bootstrap';
import { t } from '../../locales';
const propTypes = {
user: PropTypes.object.isRequired,
@ -10,7 +11,7 @@ export default function Security({ user }) {
<div>
<div className="roles">
<h4>
Roles <Badge>{Object.keys(user.roles).length}</Badge>
{t('Roles')} <Badge>{Object.keys(user.roles).length}</Badge>
</h4>
{Object.keys(user.roles).map(role => <Label key={role}>{role}</Label>)}
<hr />
@ -19,7 +20,7 @@ export default function Security({ user }) {
{user.permissions.database_access &&
<div>
<h4>
Databases <Badge>{user.permissions.database_access.length}</Badge>
{t('Databases')} <Badge>{user.permissions.database_access.length}</Badge>
</h4>
{user.permissions.database_access.map(role => <Label key={role}>{role}</Label>)}
<hr />
@ -30,7 +31,7 @@ export default function Security({ user }) {
{user.permissions.datasource_access &&
<div>
<h4>
Datasources <Badge>{user.permissions.datasource_access.length}</Badge>
{t('Datasources')} <Badge>{user.permissions.datasource_access.length}</Badge>
</h4>
{user.permissions.datasource_access.map(role => <Label key={role}>{role}</Label>)}
</div>

View File

@ -3,6 +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';
const propTypes = {
user: PropTypes.object.isRequired,
@ -14,7 +15,7 @@ const UserInfo = ({ user }) => (
email={user.email}
width="100%"
height=""
alt="Profile picture provided by Gravatar"
alt={t('Profile picture provided by Gravatar')}
className="img-rounded"
style={{ borderRadius: 15 }}
/>
@ -29,7 +30,7 @@ const UserInfo = ({ user }) => (
</h4>
<hr />
<p>
<i className="fa fa-clock-o" /> joined {moment(user.createdOn, 'YYYYMMDD').fromNow()}
<i className="fa fa-clock-o" /> {t('joined')} {moment(user.createdOn, 'YYYYMMDD').fromNow()}
</p>
<p className="email">
<i className="fa fa-envelope-o" /> {user.email}
@ -39,7 +40,7 @@ const UserInfo = ({ user }) => (
</p>
<p>
<i className="fa fa-key" />&nbsp;
<span className="text-muted">id:</span>&nbsp;
<span className="text-muted">{t('id:')}</span>&nbsp;
<span className="user-id">{user.userId}</span>
</p>
</Panel>

View File

@ -1,7 +1,7 @@
/* eslint no-unused-vars: 0 */
import React from 'react';
import ReactDOM from 'react-dom';
import { Badge, Col, Label, Row, Tabs, Tab, Panel } from 'react-bootstrap';
import App from './components/App';
import { appSetup } from '../common';

View File

@ -54,6 +54,8 @@
"datamaps": "^0.5.8",
"datatables.net-bs": "^1.10.15",
"immutable": "^3.8.1",
"jed": "^1.1.1",
"po2json": "^0.4.5",
"jquery": "3.1.1",
"lodash.throttle": "^4.1.1",
"moment": "^2.14.1",
@ -85,6 +87,7 @@
"redux-localstorage": "^0.4.1",
"redux-thunk": "^2.1.0",
"shortid": "^2.2.6",
"sprintf-js": "^1.1.1",
"supercluster": "https://github.com/georgeke/supercluster/tarball/ac3492737e7ce98e07af679623aad452373bbc40",
"urijs": "^1.18.10",
"viewport-mercator-project": "^2.1.0"

View File

@ -1,6 +1,6 @@
import React from 'react';
import Select from 'react-select';
import { mount, shallow } from 'enzyme';
import { shallow } from 'enzyme';
import { describe, it } from 'mocha';
import { expect } from 'chai';
import sinon from 'sinon';
@ -11,10 +11,12 @@ describe('AsyncSelect', () => {
const mockedProps = {
dataEndpoint: '/slicemodelview/api/read',
onChange: sinon.spy(),
placeholder: 'Select...',
mutator: () => [
{ value: 1, label: 'main' },
{ value: 2, label: 'another' },
],
valueRenderer: opt => opt.label,
};
it('is valid element', () => {
expect(
@ -49,34 +51,34 @@ describe('AsyncSelect', () => {
server.restore();
});
it('should be off by default', () => {
const wrapper = mount(
const wrapper = shallow(
<AsyncSelect {...mockedProps} />,
);
wrapper.instance().fetchOptions();
const spy = sinon.spy(wrapper.instance(), 'onChange');
expect(spy.callCount).to.equal(0);
});
it('should auto select first option', () => {
const wrapper = mount(
const wrapper = shallow(
<AsyncSelect {...mockedProps} autoSelect />,
);
const spy = sinon.spy(wrapper.instance(), 'onChange');
wrapper.instance().fetchOptions();
server.respond();
expect(spy.callCount).to.equal(1);
expect(spy.calledWith(wrapper.instance().state.options[0])).to.equal(true);
});
it('should not auto select when value prop is set', () => {
const wrapper = mount(
const wrapper = shallow(
<AsyncSelect {...mockedProps} value={2} autoSelect />,
);
const spy = sinon.spy(wrapper.instance(), 'onChange');
wrapper.instance().fetchOptions();
server.respond();
expect(spy.callCount).to.equal(0);
expect(wrapper.find(Select)).to.have.length(1);
expect(wrapper.find('.Select-value-label').children().first().text()).to.equal('another');
});
});
});

View File

@ -19,7 +19,7 @@ describe('EditableTitle', () => {
},
};
const editableWrapper = shallow(<EditableTable {...mockProps} />);
const notEditableWrapper = shallow(<EditableTable title="my title" />);
const notEditableWrapper = shallow(<EditableTable title="my title" onSaveTitle={callback} />);
it('is valid', () => {
expect(
React.isValidElement(<EditableTable {...mockProps} />),

View File

@ -1,9 +1,8 @@
import React from 'react';
import { Overlay, Popover, FormControl } from 'react-bootstrap';
import { shallow, mount } from 'enzyme';
import { shallow } from 'enzyme';
import { describe, it } from 'mocha';
import { expect } from 'chai';
import SaveQuery from '../../../javascripts/SqlLab/components/SaveQuery';
describe('SavedQuery', () => {
@ -30,11 +29,11 @@ describe('SavedQuery', () => {
expect(wrapper.find(Popover)).to.have.length(1);
});
it('pops and hides', () => {
const wrapper = mount(<SaveQuery {...mockedProps} />);
const wrapper = shallow(<SaveQuery {...mockedProps} />);
expect(wrapper.state().showSave).to.equal(false);
wrapper.find('.toggleSave').simulate('click');
wrapper.find('.toggleSave').simulate('click', { target: { value: 'test' } });
expect(wrapper.state().showSave).to.equal(true);
wrapper.find('.toggleSave').simulate('click');
wrapper.find('.toggleSave').simulate('click', { target: { value: 'test' } });
expect(wrapper.state().showSave).to.equal(false);
});
it('has a cancel button', () => {

View File

@ -2,6 +2,7 @@ import React from 'react';
import { mount } from 'enzyme';
import { describe, it, beforeEach } from 'mocha';
import { expect } from 'chai';
import sinon from 'sinon';
import Timer from '../../../javascripts/components/Timer';
import { now } from '../../../javascripts/modules/dates';
@ -9,16 +10,21 @@ import { now } from '../../../javascripts/modules/dates';
describe('Timer', () => {
let wrapper;
let clock;
const mockedProps = {
startTime: now(),
endTime: null,
isRunning: true,
status: 'warning',
};
beforeEach(() => {
clock = sinon.useFakeTimers();
mockedProps.startTime = now() + 1;
wrapper = mount(<Timer {...mockedProps} />);
});
afterEach(() => {
clock.restore();
});
it('is a valid element', () => {
expect(React.isValidElement(<Timer {...mockedProps} />)).to.equal(true);
@ -26,9 +32,8 @@ describe('Timer', () => {
it('componentWillMount starts timer after 30ms and sets state.clockStr', () => {
expect(wrapper.state().clockStr).to.equal('');
setTimeout(() => {
expect(wrapper.state().clockStr).not.equal('');
}, 31);
clock.tick(31);
expect(wrapper.state().clockStr).not.equal('');
});
it('calls startTimer on mount', () => {

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, it } from 'mocha';
import { describe, it } from 'mocha';
import { expect } from 'chai';
import * as r from '../../../javascripts/SqlLab/reducers';
@ -9,7 +9,9 @@ describe('sqlLabReducer', () => {
describe('CLONE_QUERY_TO_NEW_TAB', () => {
const testQuery = { sql: 'SELECT * FROM...', dbId: 1, id: 'flasj233' };
let newState = Object.assign({}, initialState, { queries: { [testQuery.id]: testQuery } });
newState = r.sqlLabReducer(newState, actions.cloneQueryToNewTab(testQuery));
beforeEach(() => {
newState = r.sqlLabReducer(newState, actions.cloneQueryToNewTab(testQuery));
});
it('should have at most one more tab', () => {
expect(newState.queryEditors).have.length(2);

View File

@ -9,6 +9,7 @@ import {
EVENT_NAME,
ENTITY_ID,
} from '@data-ui/event-flow';
import { t } from '../javascripts/locales';
/*
* This function takes the slice object and json payload as input and renders a
@ -52,7 +53,7 @@ function renderEventFlow(slice, json) {
Component = <ResponsiveVis data={cleanData} initialMinEventCount={minEventCount} />;
} else {
Component = <div>Sorry, there appears to be no data</div>;
Component = <div>{t('Sorry, there appears to be no data')}</div>;
}
ReactDOM.render(Component, container);

View File

@ -8,6 +8,7 @@ import { Button } from 'react-bootstrap';
import { TIME_CHOICES } from './constants';
import './filter_box.css';
import { t } from '../javascripts/locales';
const propTypes = {
origSelectedValues: PropTypes.object,
@ -102,7 +103,7 @@ class FilterBox extends React.Component {
<div key={filter} className="m-b-5">
{this.props.datasource.verbose_map[filter] || filter}
<Select.Creatable
placeholder={`Select [${filter}]`}
placeholder={t('Select [%s]', filter)}
key={filter}
multi
value={this.state.selectedValues[filter]}

View File

@ -159,8 +159,8 @@ BABEL_DEFAULT_FOLDER = 'babel/translations'
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

View File

@ -9,7 +9,7 @@
<div class="f16"><i class="flag {{languages[locale].get('flag')}}"></i><b class="caret"></b>
</div>
</a>
<ul class="dropdown-menu">
<ul class="dropdown-menu" id="language-picker">
<li class="dropdown">
{% for lang in languages %}
{% if lang != locale %}

View File

@ -2,6 +2,7 @@
{% block body %}
<div
id="app"
class="dashboard container-fluid"
data-bootstrap="{{ bootstrap_data }}"
>

View File

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import json
import os
# Global caching for JSON language packs
ALL_LANGUAGE_PACKS = {'en': {}}
DIR = os.path.dirname(os.path.abspath(__file__))
def get_language_pack(locale):
"""Get/cache a language pack
Returns the langugage pack from cache if it exists, caches otherwise
>>> get_language_pack('fr')['Dashboards']
"Tableaux de bords"
"""
pack = ALL_LANGUAGE_PACKS.get(locale)
if not pack:
filename = DIR + '/{}/LC_MESSAGES/messages.json'.format(locale)
with open(filename) as f:
try:
pack = json.load(f)
ALL_LANGUAGE_PACKS[locale] = pack
except Exception:
# Assuming english, client side falls back on english
pass
return pack

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@ import traceback
from flask import g, redirect, Response, flash, abort, get_flashed_messages
from flask_babel import gettext as __
from flask_babel import lazy_gettext as _
from flask_babel import get_locale
from flask_appbuilder import BaseView
from flask_appbuilder import ModelView
@ -16,6 +17,7 @@ from flask_appbuilder.models.sqla.filters import BaseFilter
from superset import appbuilder, conf, db, utils, sm, sql_parse
from superset.connectors.connector_registry import ConnectorRegistry
from superset.connectors.sqla.models import SqlaTable
from superset.translations.utils import get_language_pack
FRONTEND_CONF_KEYS = ('SUPERSET_WEBSERVER_TIMEOUT',)
@ -191,9 +193,12 @@ class BaseSupersetView(BaseView):
def common_bootsrap_payload(self):
"""Common data always sent to the client"""
messages = get_flashed_messages(with_categories=True)
locale = str(get_locale())
return {
'flash_messages': messages,
'conf': {k: conf.get(k) for k in FRONTEND_CONF_KEYS},
'locale': locale,
'language_pack': get_language_pack(locale),
}