chore: Localization of superset pt. 2 (#22772)

This commit is contained in:
Artem Shumeiko 2023-01-30 19:20:43 +03:00 committed by GitHub
parent b94052e438
commit c839d0daf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 260 additions and 157 deletions

View File

@ -81,7 +81,7 @@ const columnWidth: ControlFormItemSpec<'InputNumber'> = {
"Default minimal column width in pixels, actual width may still be larger than this if other columns don't need much space",
),
width: 120,
placeholder: 'auto',
placeholder: t('auto'),
debounceDelay: 400,
validators: [validateNumber],
};

View File

@ -19,7 +19,7 @@
import PropTypes from 'prop-types';
import { extent as d3Extent, range as d3Range } from 'd3-array';
import { select as d3Select } from 'd3-selection';
import { getSequentialSchemeRegistry } from '@superset-ui/core';
import { getSequentialSchemeRegistry, t } from '@superset-ui/core';
import CalHeatMap from './vendor/cal-heatmap';
const propTypes = {
@ -85,10 +85,12 @@ function Calendar(element, props) {
const metricsData = data.data;
const METRIC_TEXT = t('Metric');
Object.keys(metricsData).forEach(metric => {
const calContainer = div.append('div');
if (showMetricName) {
calContainer.text(`Metric: ${verboseMap[metric] || metric}`);
calContainer.text(`${METRIC_TEXT}: ${verboseMap[metric] || metric}`);
}
const timestamps = metricsData[metric];
const extents = d3Extent(Object.keys(timestamps), key => timestamps[key]);

View File

@ -9,7 +9,7 @@
/* eslint-disable */
import d3tip from 'd3-tip';
import { getContrastingColor } from '@superset-ui/core';
import { getContrastingColor, t } from '@superset-ui/core';
var d3 = typeof require === 'function' ? require('d3') : window.d3;
@ -256,9 +256,9 @@ var CalHeatMap = function () {
// Formatting of the title displayed when hovering a legend cell
legendTitleFormat: {
lower: 'less than {min} {name}',
inner: 'between {down} and {up} {name}',
upper: 'more than {max} {name}',
lower: t('less than {min} {name}'),
inner: t('between {down} and {up} {name}'),
upper: t('more than {max} {name}'),
},
// Animation duration, in ms

View File

@ -111,7 +111,7 @@ class CustomHistogram extends React.PureComponent {
renderTooltip={({ datum, color }) => (
<div>
<strong style={{ color }}>
{datum.bin0} to {datum.bin1}
{datum.bin0} {t('to')} {datum.bin1}
</strong>
<div>
<strong>{t('count')} </strong>

View File

@ -24,6 +24,7 @@ import {
NumberFormats,
CategoricalColorNamespace,
getSequentialSchemeRegistry,
t,
} from '@superset-ui/core';
import wrapSvgText from './utils/wrapSvgText';
@ -381,7 +382,10 @@ function Sunburst(element, props) {
.append('text')
.attr('class', 'path-abs-percent')
.attr('y', yOffsets[offsetIndex])
.text(`${absolutePercString} of total`);
// eslint-disable-next-line prefer-template
.text(absolutePercString + ' ' + t('of total'));
const OF_PARENT_TEXT = t('of parent');
if (conditionalPercString) {
offsetIndex += 1;
@ -389,7 +393,7 @@ function Sunburst(element, props) {
.append('text')
.attr('class', 'path-cond-percent')
.attr('y', yOffsets[offsetIndex])
.text(`${conditionalPercString} of parent`);
.text(`${conditionalPercString} ${OF_PARENT_TEXT}`);
}
offsetIndex += 1;

View File

@ -50,7 +50,7 @@ const radarMetricMaxValue: { name: string; config: ControlFormItemSpec } = {
'The maximum value of metrics. It is an optional configuration',
),
width: 120,
placeholder: 'auto',
placeholder: t('auto'),
debounceDelay: 400,
validators: [validateNumber],
},

View File

@ -52,7 +52,7 @@ function createAxisTitleControl(axis: 'x' | 'y'): ControlSetRow[] {
const isXAxis = axis === 'x';
const isVertical = (controls: ControlStateMapping) =>
Boolean(controls?.orientation.value === OrientationType.vertical);
const isHorizental = (controls: ControlStateMapping) =>
const isHorizontal = (controls: ControlStateMapping) =>
Boolean(controls?.orientation.value === OrientationType.horizontal);
return [
[
@ -65,7 +65,7 @@ function createAxisTitleControl(axis: 'x' | 'y'): ControlSetRow[] {
default: '',
description: t('Changing this control takes effect instantly'),
visibility: ({ controls }: ControlPanelsContainerProps) =>
isXAxis ? isVertical(controls) : isHorizental(controls),
isXAxis ? isVertical(controls) : isHorizontal(controls),
},
},
],
@ -82,7 +82,7 @@ function createAxisTitleControl(axis: 'x' | 'y'): ControlSetRow[] {
choices: formatSelectOptions(sections.TITLE_MARGIN_OPTIONS),
description: t('Changing this control takes effect instantly'),
visibility: ({ controls }: ControlPanelsContainerProps) =>
isXAxis ? isVertical(controls) : isHorizental(controls),
isXAxis ? isVertical(controls) : isHorizontal(controls),
},
},
],
@ -96,7 +96,7 @@ function createAxisTitleControl(axis: 'x' | 'y'): ControlSetRow[] {
default: '',
description: t('Changing this control takes effect instantly'),
visibility: ({ controls }: ControlPanelsContainerProps) =>
isXAxis ? isHorizental(controls) : isVertical(controls),
isXAxis ? isHorizontal(controls) : isVertical(controls),
},
},
],
@ -113,7 +113,7 @@ function createAxisTitleControl(axis: 'x' | 'y'): ControlSetRow[] {
choices: formatSelectOptions(sections.TITLE_MARGIN_OPTIONS),
description: t('Changing this control takes effect instantly'),
visibility: ({ controls }: ControlPanelsContainerProps) =>
isXAxis ? isHorizental(controls) : isVertical(controls),
isXAxis ? isHorizontal(controls) : isVertical(controls),
},
},
],
@ -130,7 +130,7 @@ function createAxisTitleControl(axis: 'x' | 'y'): ControlSetRow[] {
choices: sections.TITLE_POSITION_OPTIONS,
description: t('Changing this control takes effect instantly'),
visibility: ({ controls }: ControlPanelsContainerProps) =>
isXAxis ? isHorizental(controls) : isVertical(controls),
isXAxis ? isHorizontal(controls) : isVertical(controls),
},
},
],
@ -141,7 +141,7 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] {
const isXAxis = axis === 'x';
const isVertical = (controls: ControlStateMapping) =>
Boolean(controls?.orientation.value === OrientationType.vertical);
const isHorizental = (controls: ControlStateMapping) =>
const isHorizontal = (controls: ControlStateMapping) =>
Boolean(controls?.orientation.value === OrientationType.horizontal);
return [
[
@ -154,7 +154,7 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] {
'When using other than adaptive formatting, labels may overlap.',
)}`,
visibility: ({ controls }: ControlPanelsContainerProps) =>
isXAxis ? isVertical(controls) : isHorizental(controls),
isXAxis ? isVertical(controls) : isHorizontal(controls),
},
},
],
@ -176,7 +176,7 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] {
'Input field supports custom rotation. e.g. 30 for 30°',
),
visibility: ({ controls }: ControlPanelsContainerProps) =>
isXAxis ? isVertical(controls) : isHorizental(controls),
isXAxis ? isVertical(controls) : isHorizontal(controls),
},
},
],
@ -187,7 +187,7 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] {
...sharedControls.y_axis_format,
label: t('Axis Format'),
visibility: ({ controls }: ControlPanelsContainerProps) =>
isXAxis ? isHorizental(controls) : isVertical(controls),
isXAxis ? isHorizontal(controls) : isVertical(controls),
},
},
],
@ -201,7 +201,7 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] {
default: logAxis,
description: t('Logarithmic axis'),
visibility: ({ controls }: ControlPanelsContainerProps) =>
isXAxis ? isHorizental(controls) : isVertical(controls),
isXAxis ? isHorizontal(controls) : isVertical(controls),
},
},
],
@ -215,7 +215,7 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] {
default: minorSplitLine,
description: t('Draw split lines for minor axis ticks'),
visibility: ({ controls }: ControlPanelsContainerProps) =>
isXAxis ? isHorizental(controls) : isVertical(controls),
isXAxis ? isHorizontal(controls) : isVertical(controls),
},
},
],
@ -229,7 +229,7 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] {
renderTrigger: true,
description: t('Its not recommended to truncate axis in Bar chart.'),
visibility: ({ controls }: ControlPanelsContainerProps) =>
isXAxis ? isHorizental(controls) : isVertical(controls),
isXAxis ? isHorizontal(controls) : isVertical(controls),
},
},
],
@ -249,7 +249,7 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] {
),
visibility: ({ controls }: ControlPanelsContainerProps) =>
Boolean(controls?.truncateYAxis?.value) &&
(isXAxis ? isHorizental(controls) : isVertical(controls)),
(isXAxis ? isHorizontal(controls) : isVertical(controls)),
},
},
],

View File

@ -29,6 +29,7 @@ import {
useTheme,
isAdhocColumn,
BinaryQueryObjectFilterClause,
t,
} from '@superset-ui/core';
import { PivotTable, sortAs, aggregatorTemplates } from './react-pivottable';
import {
@ -55,7 +56,7 @@ const PivotTableWrapper = styled.div`
overflow: auto;
`;
const METRIC_KEY = 'metric';
const METRIC_KEY = t('metric');
const vals = ['value'];
const StyledPlusSquareOutlined = styled(PlusSquareOutlined)`

View File

@ -487,7 +487,9 @@ export class TableRenderer extends React.Component {
true,
)}
>
{`Total (${this.props.aggregatorName})`}
{t('Total (%(aggregatorName)s)', {
aggregatorName: t(this.props.aggregatorName),
})}
</th>
) : null;
@ -550,7 +552,9 @@ export class TableRenderer extends React.Component {
)}
>
{colAttrs.length === 0
? `Total (${this.props.aggregatorName})`
? t('Total (%(aggregatorName)s)', {
aggregatorName: t(this.props.aggregatorName),
})
: null}
</th>
</tr>
@ -764,10 +768,9 @@ export class TableRenderer extends React.Component {
true,
)}
>
{
// eslint-disable-next-line prefer-template
t('Total') + ` (${this.props.aggregatorName})`
}
{t('Total (%(aggregatorName)s)', {
aggregatorName: t(this.props.aggregatorName),
})}
</th>
);

View File

@ -17,6 +17,7 @@
* under the License.
*/
import React from 'react';
import { t } from '@superset-ui/core';
import { formatSelectOptions } from '@superset-ui/chart-controls';
export type SizeOption = [number, string];
@ -34,7 +35,7 @@ function DefaultSelectRenderer({
}: SelectPageSizeRendererProps) {
return (
<span className="dt-select-page-size form-inline">
Show{' '}
{t('Show')}{' '}
<select
className="form-control input-sm"
value={current}
@ -54,7 +55,7 @@ function DefaultSelectRenderer({
);
})}
</select>{' '}
entries
{t('entries')}
</span>
);
}

View File

@ -198,7 +198,7 @@ function SelectPageSize({
}
const getNoResultsMessage = (filter: string) =>
t(filter ? 'No matching records found' : 'No records found');
filter ? t('No matching records found') : t('No records found');
export default function TableChart<D extends DataRecord = DataRecord>(
props: TableChartTransformedProps<D> & {

View File

@ -1412,7 +1412,7 @@ export function popQuery(queryId) {
dbId: queryData.database.id,
schema: queryData.schema,
sql: queryData.sql,
name: `Copy of ${queryData.tab_name}`,
name: t('Copy of %s', queryData.tab_name),
autorun: false,
};
return dispatch(addQueryEditor(queryEditorProps));
@ -1422,6 +1422,7 @@ export function popQuery(queryId) {
}
export function popDatasourceQuery(datasourceKey, sql) {
return function (dispatch) {
const QUERY_TEXT = t('Query');
const datasetId = datasourceKey.split('__')[0];
return SupersetClient.get({
endpoint: `/api/v1/dataset/${datasetId}?q=(keys:!(none))`,
@ -1429,7 +1430,7 @@ export function popDatasourceQuery(datasourceKey, sql) {
.then(({ json }) =>
dispatch(
addQueryEditor({
name: `Query ${json.result.name}`,
name: `${QUERY_TEXT} ${json.result.name}`,
dbId: json.result.database.id,
schema: json.result.schema,
autorun: sql !== undefined,

View File

@ -18,7 +18,7 @@
*/
import React from 'react';
import Label from 'src/components/Label';
import { STATE_TYPE_MAP } from 'src/SqlLab/constants';
import { STATE_TYPE_MAP, STATE_TYPE_MAP_LOCALIZED } from 'src/SqlLab/constants';
import { styled, Query } from '@superset-ui/core';
interface QueryStateLabelProps {
@ -31,6 +31,8 @@ const StyledLabel = styled(Label)`
export default function QueryStateLabel({ query }: QueryStateLabelProps) {
return (
<StyledLabel type={STATE_TYPE_MAP[query.state]}>{query.state}</StyledLabel>
<StyledLabel type={STATE_TYPE_MAP[query.state]}>
{STATE_TYPE_MAP_LOCALIZED[query.state]}
</StyledLabel>
);
}

View File

@ -360,8 +360,8 @@ const ResultSet = ({
message={t('%(rows)d rows returned', { rows })}
onClose={() => setAlertIsOpen(false)}
description={t(
'The number of rows displayed is limited to %s by the dropdown.',
rows,
'The number of rows displayed is limited to %(rows)d by the dropdown.',
{ rows },
)}
/>
</div>

View File

@ -35,6 +35,7 @@ import {
STATUS_OPTIONS,
STATE_TYPE_MAP,
LOCALSTORAGE_MAX_QUERY_AGE_MS,
STATUS_OPTIONS_LOCALIZED,
} from '../../constants';
const TAB_HEIGHT = 140;
@ -145,7 +146,7 @@ const SouthPane = ({
};
const renderOfflineStatus = () => (
<Label className="m-r-3" type={STATE_TYPE_MAP[STATUS_OPTIONS.offline]}>
{STATUS_OPTIONS.offline}
{STATUS_OPTIONS_LOCALIZED.offline}
</Label>
);

View File

@ -16,6 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@superset-ui/core';
export const STATE_TYPE_MAP = {
offline: 'danger',
failed: 'danger',
@ -26,6 +28,16 @@ export const STATE_TYPE_MAP = {
success: 'success',
};
export const STATE_TYPE_MAP_LOCALIZED = {
offline: t('offline'),
failed: t('failed'),
pending: t('pending'),
fetching: t('fetching'),
running: t('running'),
stopped: t('stopped'),
success: t('success'),
};
export const STATUS_OPTIONS = {
success: 'success',
failed: 'failed',
@ -34,6 +46,14 @@ export const STATUS_OPTIONS = {
pending: 'pending',
};
export const STATUS_OPTIONS_LOCALIZED = {
success: t('success'),
failed: t('failed'),
running: t('running'),
offline: t('offline'),
pending: t('pending'),
};
export const TIME_OPTIONS = [
'now',
'1 hour ago',

View File

@ -248,7 +248,7 @@ export default function DatabaseSelector({
setSchemaOptions(options);
setLoadingSchemas(false);
if (options.length === 1) changeSchema(options[0]);
if (refresh > 0) addSuccessToast('List refreshed');
if (refresh > 0) addSuccessToast(t('List refreshed'));
})
.catch(err => {
setLoadingSchemas(false);
@ -321,6 +321,7 @@ export default function DatabaseSelector({
labelInValue
loading={loadingSchemas}
name="select-schema"
notFoundContent={t('No compatible schema found')}
placeholder={t('Select schema or type schema name')}
onChange={item => changeSchema(item as SchemaValue)}
options={schemaOptions}

View File

@ -190,7 +190,7 @@ const ChangeDatasourceModal: FunctionComponent<ChangeDatasourceModalProps> = ({
);
});
onHide();
addSuccessToast('Successfully changed dataset!');
addSuccessToast(t('Successfully changed dataset!'));
};
const handlerCancelConfirm = () => {

View File

@ -491,7 +491,7 @@ ColumnCollectionTable.defaultProps = {
allowAddItem: false,
allowEditDataType: false,
itemGenerator: () => ({
column_name: '<new column>',
column_name: t('<new column>'),
filterable: true,
groupby: true,
}),
@ -976,8 +976,8 @@ class DatasourceEditor extends React.PureComponent {
tableColumns={['name', 'config']}
onChange={this.onDatasourcePropChange.bind(this, 'spatials')}
itemGenerator={() => ({
name: '<new spatial>',
type: '<no type>',
name: t('<new spatial>'),
type: t('<no type>'),
config: null,
})}
collection={spatials}
@ -1256,7 +1256,7 @@ class DatasourceEditor extends React.PureComponent {
allowAddItem
onChange={this.onDatasourcePropChange.bind(this, 'metrics')}
itemGenerator={() => ({
metric_name: '<new metric>',
metric_name: t('<new metric>'),
verbose_name: '',
expression: '',
})}
@ -1418,10 +1418,10 @@ class DatasourceEditor extends React.PureComponent {
allowAddItem
allowEditDataType
itemGenerator={() => ({
column_name: '<new column>',
column_name: t('<new column>'),
filterable: true,
groupby: true,
expression: '<enter SQL expression here>',
expression: t('<enter SQL expression here>'),
__expanded: true,
})}
/>

View File

@ -177,6 +177,8 @@ const DatasourceModal: FunctionComponent<DatasourceModalProps> = ({
content: renderSaveDialog(),
onOk: onConfirmSave,
icon: null,
okText: t('OK'),
cancelText: t('Cancel'),
});
};

View File

@ -23,7 +23,7 @@ import React, {
useImperativeHandle,
} from 'react';
import moment, { Moment } from 'moment';
import { styled } from '@superset-ui/core';
import { styled, t } from '@superset-ui/core';
import { RangePicker } from 'src/components/DatePicker';
import { FormLabel } from 'src/components/Form';
import { BaseFilter, FilterHandler } from './Base';
@ -64,6 +64,7 @@ function DateRangeFilter(
<RangeFilterContainer>
<FormLabel>{Header}</FormLabel>
<RangePicker
placeholder={[t('Start date'), t('End date')]}
showTime
value={momentValue}
onChange={momentRange => {

View File

@ -16,6 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import { t } from '@superset-ui/core';
import { BootstrapData, CommonBootstrapData } from './types/bootstrapTypes';
export const DATETIME_WITH_TIME_ZONE = 'YYYY-MM-DD HH:mm:ssZ';
@ -142,7 +144,7 @@ export const SLOW_DEBOUNCE = 500;
/**
* Display null as `N/A`
*/
export const NULL_DISPLAY = 'N/A';
export const NULL_DISPLAY = t('N/A');
export const DEFAULT_COMMON_BOOTSTRAP_DATA: CommonBootstrapData = {
flash_messages: [],

View File

@ -652,7 +652,10 @@ export function maxUndoHistoryToast() {
return dispatch(
addWarningToast(
`You have used all ${historyLength} undo slots and will not be able to fully undo subsequent actions. You may save your current state to reset the history.`,
t(
'You have used all %(historyLength)s undo slots and will not be able to fully undo subsequent actions. You may save your current state to reset the history.',
{ historyLength },
),
),
);
};

View File

@ -81,7 +81,7 @@ class SaveModal extends React.PureComponent<SaveModalProps, SaveModalState> {
super(props);
this.state = {
saveType: props.saveType,
newDashName: `${props.dashboardTitle} [copy]`,
newDashName: props.dashboardTitle + t('[copy]'),
duplicateSlices: false,
};

View File

@ -18,7 +18,7 @@
*/
import React from 'react';
import cx from 'classnames';
import { css, styled } from '@superset-ui/core';
import { css, styled, t } from '@superset-ui/core';
import backgroundStyleOptions from 'src/dashboard/util/backgroundStyleOptions';
import PopoverDropdown, {
@ -73,11 +73,12 @@ const BackgroundStyleOption = styled.div`
`;
function renderButton(option: OptionProps) {
const BACKGROUND_TEXT = t('background');
return (
<BackgroundStyleOption
className={cx('background-style-option', option.className)}
>
{`${option.label} background`}
{`${option.label} ${BACKGROUND_TEXT}`}
</BackgroundStyleOption>
);
}

View File

@ -90,6 +90,7 @@ const DatasetSelect = ({ onChange, value }: DatasetSelectProps) => {
value={value}
options={loadDatasetOptions}
onChange={onChange}
notFoundContent={t('No compatible datasets found')}
/>
);
};

View File

@ -944,7 +944,9 @@ const FiltersConfigForm = (
<StyledRowFormItem
name={['filters', filterId, 'time_range']}
label={<StyledLabel>{t('Time range')}</StyledLabel>}
initialValue={filterToEdit?.time_range || 'No filter'}
initialValue={
filterToEdit?.time_range || t('No filter')
}
required={!hasAdhoc}
rules={[
{

View File

@ -46,7 +46,7 @@ const typeToDefaultMetaData = {
},
[DIVIDER_TYPE]: null,
[HEADER_TYPE]: {
text: 'New header',
text: t('New header'),
headerSize: MEDIUM_HEADER,
background: BACKGROUND_TRANSPARENT,
},

View File

@ -56,33 +56,39 @@ export interface OperatorType {
export const OPERATOR_ENUM_TO_OPERATOR_TYPE: {
[key in Operators]: OperatorType;
} = {
[Operators.EQUALS]: { display: 'Equal to (=)', operation: '==' },
[Operators.NOT_EQUALS]: { display: 'Not equal to (≠)', operation: '!=' },
[Operators.LESS_THAN]: { display: 'Less than (<)', operation: '<' },
[Operators.EQUALS]: { display: t('Equal to (=)'), operation: '==' },
[Operators.NOT_EQUALS]: { display: t('Not equal to (≠)'), operation: '!=' },
[Operators.LESS_THAN]: { display: t('Less than (<)'), operation: '<' },
[Operators.LESS_THAN_OR_EQUAL]: {
display: 'Less or equal (<=)',
display: t('Less or equal (<=)'),
operation: '<=',
},
[Operators.GREATER_THAN]: { display: 'Greater than (>)', operation: '>' },
[Operators.GREATER_THAN]: { display: t('Greater than (>)'), operation: '>' },
[Operators.GREATER_THAN_OR_EQUAL]: {
display: 'Greater or equal (>=)',
display: t('Greater or equal (>=)'),
operation: '>=',
},
[Operators.IN]: { display: 'In', operation: 'IN' },
[Operators.NOT_IN]: { display: 'Not in', operation: 'NOT IN' },
[Operators.LIKE]: { display: 'Like', operation: 'LIKE' },
[Operators.ILIKE]: { display: 'Like (case insensitive)', operation: 'ILIKE' },
[Operators.REGEX]: { display: 'Regex', operation: 'REGEX' },
[Operators.IS_NOT_NULL]: { display: 'Is not null', operation: 'IS NOT NULL' },
[Operators.IS_NULL]: { display: 'Is null', operation: 'IS NULL' },
[Operators.IN]: { display: t('In'), operation: 'IN' },
[Operators.NOT_IN]: { display: t('Not in'), operation: 'NOT IN' },
[Operators.LIKE]: { display: t('Like'), operation: 'LIKE' },
[Operators.ILIKE]: {
display: t('Like (case insensitive)'),
operation: 'ILIKE',
},
[Operators.REGEX]: { display: t('Regex'), operation: 'REGEX' },
[Operators.IS_NOT_NULL]: {
display: t('Is not null'),
operation: 'IS NOT NULL',
},
[Operators.IS_NULL]: { display: t('Is null'), operation: 'IS NULL' },
[Operators.LATEST_PARTITION]: {
display: 'use latest_partition template',
display: t('use latest_partition template'),
operation: 'LATEST PARTITION',
},
[Operators.IS_TRUE]: { display: 'Is true', operation: '==' },
[Operators.IS_FALSE]: { display: 'Is false', operation: '==' },
[Operators.IS_TRUE]: { display: t('Is true'), operation: '==' },
[Operators.IS_FALSE]: { display: t('Is false'), operation: '==' },
[Operators.TEMPORAL_RANGE]: {
display: 'TEMPORAL_RANGE',
display: t('TEMPORAL_RANGE'),
operation: 'TEMPORAL_RANGE',
},
};

View File

@ -18,6 +18,7 @@
*/
import React from 'react';
import moment from 'moment';
import { t } from '@superset-ui/core';
import rison from 'rison';
import TableLoader from '../../components/TableLoader';
@ -48,6 +49,7 @@ export default function RecentActivity({ user }: RecentActivityProps) {
mutator={mutator}
sortable
dataEndpoint={`/api/v1/log/recent_activity/${user?.userId}/?q=${params}`}
noDataText={t('No Data')}
/>
</div>
);

View File

@ -19,8 +19,11 @@
import { t } from '@superset-ui/core';
const CREATE_CHART_TEXT = t('Create chart');
const UPDATE_CHART_TEXT = t('Update chart');
export const getChartRequiredFieldsMissingMessage = (isCreating: boolean) =>
t(
'Select values in highlighted field(s) in the control panel. Then run the query by clicking on the %s button.',
isCreating ? '"Create chart"' : '"Update chart"',
`"${isCreating ? CREATE_CHART_TEXT : UPDATE_CHART_TEXT}"`,
);

View File

@ -455,7 +455,7 @@ function AlertList({
id: 'owners',
input: 'select',
operator: FilterOperator.relationManyMany,
unfilteredLabel: 'All',
unfilteredLabel: t('All'),
fetchSelects: createFetchRelated(
'report',
'owners',

View File

@ -571,7 +571,7 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
const shouldEnableForceScreenshot = contentType === 'chart' && !isReport;
const data: any = {
...currentAlert,
type: isReport ? 'Report' : 'Alert',
type: isReport ? t('Report') : t('Alert'),
force_screenshot: shouldEnableForceScreenshot || forceScreenshot,
validator_type: conditionNotNull ? 'not null' : 'operator',
validator_config_json: conditionNotNull

View File

@ -159,13 +159,23 @@ function ExecutionLog({ addDangerToast, isReportEnabled }: ExecutionLogProps) {
[isReportEnabled],
);
const path = `/${isReportEnabled ? 'report' : 'alert'}/list/`;
const ALERT_TEXT = t('Alert');
const REPORT_TEXT = t('Report');
return (
<>
<SubMenu
name={
<StyledHeader>
<span>
{alertResource?.type} {alertResource?.name}
{alertResource
? alertResource.type === 'Alert'
? `${ALERT_TEXT}:`
: alertResource.type === 'Report'
? `${REPORT_TEXT}:`
: null
: null}{' '}
{alertResource?.name}
</span>
<span>
<Link to={path}>{t('Back to all')}</Link>

View File

@ -304,6 +304,7 @@ const AnnotationModal: FunctionComponent<AnnotationModalProps> = ({
<span className="required">*</span>
</div>
<RangePicker
placeholder={[t('Start date'), t('End date')]}
format="YYYY-MM-DD HH:mm"
onChange={onDateChange}
showTime={{ format: 'hh:mm a' }}

View File

@ -285,7 +285,7 @@ function AnnotationLayersList({
id: 'created_by',
input: 'select',
operator: FilterOperator.relationOneMany,
unfilteredLabel: 'All',
unfilteredLabel: t('All'),
fetchSelects: createFetchRelated(
'annotation_layer',
'created_by',

View File

@ -275,7 +275,7 @@ function CssTemplatesList({
id: 'created_by',
input: 'select',
operator: FilterOperator.relationOneMany,
unfilteredLabel: 'All',
unfilteredLabel: t('All'),
fetchSelects: createFetchRelated(
'css_template',
'created_by',

View File

@ -117,7 +117,9 @@ export const EncryptedField = ({
name={encryptedField}
value={encryptedValue}
onChange={changeMethods.onParametersChange}
placeholder="Paste content of service credentials JSON file here"
placeholder={t(
'Paste content of service credentials JSON file here',
)}
/>
<span className="label-paste">
{t('Copy and paste the entire service account .json file here')}

View File

@ -26,14 +26,14 @@ const FIELD_TEXT_MAP = {
helpText: t(
'Copy the account name of that database you are trying to connect to.',
),
placeholder: 'e.g. world_population',
placeholder: t('e.g. world_population'),
},
warehouse: {
placeholder: 'e.g. compute_wh',
placeholder: t('e.g. compute_wh'),
className: 'form-group-w-50',
},
role: {
placeholder: 'e.g. AccountAdmin',
placeholder: t('e.g. AccountAdmin'),
className: 'form-group-w-50',
},
};

View File

@ -193,11 +193,11 @@ const SSHTunnelForm = ({
data-test="ssh-tunnel-password-input"
iconRender={visible =>
visible ? (
<Tooltip title="Hide password.">
<Tooltip title={t('Hide password.')}>
<EyeInvisibleOutlined />
</Tooltip>
) : (
<Tooltip title="Show password.">
<Tooltip title={t('Show password.')}>
<EyeOutlined />
</Tooltip>
)

View File

@ -1216,7 +1216,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
required
validationMethods={{ onBlur: () => {} }}
errorMessage={validationErrors?.confirm_overwrite}
label={t(`TYPE "OVERWRITE" TO CONFIRM`)}
label={t('Type "%s" to confirm', t('OVERWRITE'))}
onChange={confirmOverwrite}
css={formScrollableStyles}
/>
@ -1615,7 +1615,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
<Alert
closable={false}
css={(theme: SupersetTheme) => antDAlertStyles(theme)}
message="Additional fields may be required"
message={t('Additional fields may be required')}
showIcon
description={
<>

View File

@ -351,7 +351,7 @@ function QueryList({ addDangerToast }: QueryListProps) {
id: 'database',
input: 'select',
operator: FilterOperator.relationOneMany,
unfilteredLabel: 'All',
unfilteredLabel: t('All'),
fetchSelects: createFetchRelated(
'query',
'database',

View File

@ -427,7 +427,7 @@ function SavedQueryList({
id: 'database',
input: 'select',
operator: FilterOperator.relationOneMany,
unfilteredLabel: 'All',
unfilteredLabel: t('All'),
fetchSelects: createFetchRelated(
'saved_query',
'database',

View File

@ -420,6 +420,10 @@ export function useImportResource(
const formData = new FormData();
formData.append('formData', bundle);
const RE_EXPORT_TEXT = t(
'Please re-export your file and try importing again',
);
/* The import bundle never contains database passwords; if required
* they should be provided by the user during import.
*/
@ -468,7 +472,7 @@ export function useImportResource(
resourceLabel,
[
...error.errors.map(payload => payload.message),
t('Please re-export your file and try importing again.'),
RE_EXPORT_TEXT,
].join('.\n'),
),
);

View File

@ -30,6 +30,13 @@ const welcomeTableLabels: Record<WelcomeTable, string> = {
[WelcomeTable.SavedQueries]: t('saved queries'),
};
const welcomeTableEmpty: Record<WelcomeTable, string> = {
[WelcomeTable.Charts]: t('No charts yet'),
[WelcomeTable.Dashboards]: t('No dashboards yet'),
[WelcomeTable.Recents]: t('No recents yet'),
[WelcomeTable.SavedQueries]: t('No saved queries yet'),
};
export interface EmptyStateProps {
tableName: WelcomeTable;
tab?: string;
@ -75,11 +82,7 @@ export default function EmptyState({
[WelcomeTable.Recents]: 'union.svg',
[WelcomeTable.SavedQueries]: 'empty-queries.svg',
};
const mine = (
<span>
{t('No %(tableName)s yet', { tableName: welcomeTableLabels[tableName] })}
</span>
);
const mine = <span>{welcomeTableEmpty[tableName]}</span>;
const recent = (
<span className="no-recents">
{(() => {

View File

@ -30,6 +30,7 @@ from io import StringIO
from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING
import pandas as pd
from flask_babel import gettext as __
from superset.common.chart_data import ChartDataResultFormat
from superset.utils.core import (
@ -68,7 +69,7 @@ def pivot_df( # pylint: disable=too-many-locals, too-many-arguments, too-many-s
show_columns_total: bool = False,
apply_metrics_on_rows: bool = False,
) -> pd.DataFrame:
metric_name = f"Total ({aggfunc})"
metric_name = __("Total (%(aggfunc)s)", aggfunc=aggfunc)
if transpose_pivot:
rows, columns = columns, rows
@ -156,7 +157,7 @@ def pivot_df( # pylint: disable=too-many-locals, too-many-arguments, too-many-s
slice_ = df.columns.get_loc(subgroup)
subtotal = pivot_v2_aggfunc_map[aggfunc](df.iloc[:, slice_], axis=1)
depth = df.columns.nlevels - len(subgroup) - 1
total = metric_name if level == 0 else "Subtotal"
total = metric_name if level == 0 else __("Subtotal")
subtotal_name = tuple([*subgroup, total, *([""] * depth)])
# insert column after subgroup
df.insert(int(slice_.stop), subtotal_name, subtotal)
@ -173,7 +174,7 @@ def pivot_df( # pylint: disable=too-many-locals, too-many-arguments, too-many-s
df.iloc[slice_, :].apply(pd.to_numeric), axis=0
)
depth = df.index.nlevels - len(subgroup) - 1
total = metric_name if level == 0 else "Subtotal"
total = metric_name if level == 0 else __("Subtotal")
subtotal.name = tuple([*subgroup, total, *([""] * depth)])
# insert row after subgroup
df = pd.concat(

View File

@ -22,6 +22,7 @@ from enum import Enum
from typing import Any, Dict, Hashable, List, Optional, Set, Type, TYPE_CHECKING, Union
from flask_appbuilder.security.sqla.models import User
from flask_babel import gettext as __
from sqlalchemy import and_, Boolean, Column, Integer, String, Text
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import foreign, Query, relationship, RelationshipProperty, Session
@ -248,10 +249,10 @@ class BaseDatasource(
for column_name in self.column_names:
column_name = str(column_name or "")
order_by_choices.append(
(json.dumps([column_name, True]), column_name + " [asc]")
(json.dumps([column_name, True]), f"{column_name} " + __("[asc]"))
)
order_by_choices.append(
(json.dumps([column_name, False]), column_name + " [desc]")
(json.dumps([column_name, False]), f"{column_name} " + __("[desc]"))
)
verbose_map = {"__timestamp": "Time"}

View File

@ -341,7 +341,7 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
)
appbuilder.add_link(
"SQL Editor",
label=_("SQL Lab"),
label=__("SQL Lab"),
href="/superset/sqllab/",
category_icon="fa-flask",
icon="fa-flask",
@ -349,7 +349,8 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
category_label=__("SQL"),
)
appbuilder.add_link(
__("Saved Queries"),
"Saved Queries",
label=__("Saved Queries"),
href="/savedqueryview/list/",
icon="fa-save",
category="SQL Lab",
@ -357,7 +358,7 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
)
appbuilder.add_link(
"Query Search",
label=_("Query History"),
label=__("Query History"),
href="/superset/sqllab/history/",
icon="fa-search",
category_icon="fa-flask",
@ -396,7 +397,7 @@ class SupersetAppInitializer: # pylint: disable=too-many-public-methods
appbuilder.add_view(
AnnotationLayerView,
"Annotation Layers",
label=_("Annotation Layers"),
label=__("Annotation Layers"),
href="/annotationlayer/list/",
icon="fa-comment",
category_icon="",

View File

@ -26,6 +26,7 @@ import sqlalchemy as sqla
from flask import current_app, Markup
from flask_appbuilder import Model
from flask_appbuilder.models.decorators import renders
from flask_babel import gettext as __
from humanize import naturaltime
from sqlalchemy import (
Boolean,
@ -224,10 +225,10 @@ class Query(
for col in self.columns:
column_name = str(col.get("column_name") or "")
order_by_choices.append(
(json.dumps([column_name, True]), column_name + " [asc]")
(json.dumps([column_name, True]), f"{column_name} " + __("[asc]"))
)
order_by_choices.append(
(json.dumps([column_name, False]), column_name + " [desc]")
(json.dumps([column_name, False]), f"{column_name} " + __("[desc]"))
)
return {

View File

@ -485,7 +485,11 @@ def execute_sql_statements( # pylint: disable=too-many-arguments, too-many-loca
or (query.ctas_method == CtasMethod.TABLE and i == len(statements) - 1)
)
# Run statement
msg = f"Running statement {i+1} out of {statement_count}"
msg = __(
"Running statement %(statement_num)s out of %(statement_count)s",
statement_num=i + 1,
statement_count=statement_count,
)
logger.info("Query %s: %s", str(query_id), msg)
query.set_extra_json_key("progress", msg)
session.commit()
@ -504,7 +508,11 @@ def execute_sql_statements( # pylint: disable=too-many-arguments, too-many-loca
except Exception as ex: # pylint: disable=broad-except
msg = str(ex)
prefix_message = (
f"[Statement {i+1} out of {statement_count}]"
__(
"Statement %(statement_num)s out of %(statement_count)s",
statement_num=i + 1,
statement_count=statement_count,
)
if statement_count > 1
else ""
)

View File

@ -19,11 +19,11 @@ from __future__ import annotations
import os
from typing import Optional, TYPE_CHECKING
from flask_babel import lazy_gettext as _
from superset.errors import SupersetError, SupersetErrorType
from superset.exceptions import SupersetException
MSG_FORMAT = "Failed to execute {}"
if TYPE_CHECKING:
from superset.sqllab.sqllab_execution_context import SqlJsonExecutionContext
@ -61,7 +61,10 @@ class SqlLabException(SupersetException):
super().__init__(self._generate_message(), exception, error_type)
def _generate_message(self) -> str:
msg = MSG_FORMAT.format(self.sql_json_execution_context.get_query_details())
msg = _(
"Failed to execute %(query)s",
query=self.sql_json_execution_context.get_query_details(),
)
if self.failed_reason_msg:
msg = msg + self.failed_reason_msg
if self.suggestion_help_msg is not None:

View File

@ -178,7 +178,7 @@ def get_since_until( # pylint: disable=too-many-arguments,too-many-locals,too-m
_relative_start = relative_start if relative_start else "today"
_relative_end = relative_end if relative_end else "today"
if time_range == NO_TIME_RANGE:
if time_range == NO_TIME_RANGE or time_range == _(NO_TIME_RANGE):
return None, None
if time_range and time_range.startswith("Last") and separator not in time_range:

View File

@ -19,6 +19,7 @@ from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional
from flask import Flask
from flask_babel import lazy_gettext as _
from sqlalchemy import text, TypeDecorator
from sqlalchemy.engine import Connection, Dialect, Row
from sqlalchemy_utils import EncryptedType
@ -109,7 +110,13 @@ class SecretsMigrator:
return bytes(value.encode("utf8"))
# Just bail if we haven't seen this type before...
raise ValueError(f"DB column {col_name} has unknown type: {type(value)}")
raise ValueError(
_(
"DB column %(col_name)s has unknown type: %(value_type)s",
col_name=col_name,
value_type=type(value),
)
)
@staticmethod
def _select_columns_from_table(

View File

@ -23,6 +23,7 @@ from flask import request
from flask_appbuilder import expose
from flask_appbuilder.api import rison
from flask_appbuilder.security.decorators import has_access_api
from flask_babel import lazy_gettext as _
from superset import db, event_logger
from superset.charts.commands.exceptions import (
@ -105,7 +106,7 @@ class Api(BaseSupersetView):
}
return self.json_response({"result": result})
except (ValueError, TimeRangeParseFailError, TimeRangeAmbiguousError) as error:
error_msg = {"message": f"Unexpected time range: {error}"}
error_msg = {"message": _("Unexpected time range: %s" % error)}
return self.json_response(error_msg, 400)
def get_query_context_factory(self) -> QueryContextFactory:

View File

@ -66,7 +66,7 @@ class DashboardMixin: # pylint: disable=too-few-public-methods
"roles": _(
"Roles is a list which defines access to the dashboard. "
"Granting a role access to a dashboard will bypass dataset level checks."
"If no roles defined then the dashboard is available to all roles."
"If no roles are defined then the dashboard is available to all roles."
),
"published": _(
"Determines whether or not this dashboard is "

View File

@ -25,7 +25,7 @@ from flask import flash, g, redirect
from flask_appbuilder import expose, SimpleFormView
from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder.security.decorators import has_access
from flask_babel import gettext as __, lazy_gettext as _
from flask_babel import lazy_gettext as _
from werkzeug.wrappers import Response
from wtforms.fields import StringField
from wtforms.validators import ValidationError
@ -176,7 +176,7 @@ class CsvToDatabaseView(CustomFormView):
delimiter_input = form.delimiter.data
if not schema_allows_file_upload(database, csv_table.schema):
message = __(
message = _(
'Database "%(database_name)s" schema "%(schema_name)s" '
"is not allowed for csv uploads. Please contact your Superset Admin.",
database_name=database.database_name,
@ -265,7 +265,7 @@ class CsvToDatabaseView(CustomFormView):
db.session.commit()
except Exception as ex: # pylint: disable=broad-except
db.session.rollback()
message = __(
message = _(
'Unable to upload CSV file "%(filename)s" to table '
'"%(table_name)s" in database "%(db_name)s". '
"Error message: %(error_msg)s",
@ -280,7 +280,7 @@ class CsvToDatabaseView(CustomFormView):
return redirect("/csvtodatabaseview/form")
# Go back to welcome page / splash screen
message = __(
message = _(
'CSV file "%(csv_filename)s" uploaded to table "%(table_name)s" in '
'database "%(db_name)s"',
csv_filename=form.csv_file.data.filename,
@ -315,7 +315,7 @@ class ExcelToDatabaseView(SimpleFormView):
excel_table = Table(table=form.name.data, schema=form.schema.data)
if not schema_allows_file_upload(database, excel_table.schema):
message = __(
message = _(
'Database "%(database_name)s" schema "%(schema_name)s" '
"is not allowed for excel uploads. Please contact your Superset Admin.",
database_name=database.database_name,
@ -402,7 +402,7 @@ class ExcelToDatabaseView(SimpleFormView):
db.session.commit()
except Exception as ex: # pylint: disable=broad-except
db.session.rollback()
message = __(
message = _(
'Unable to upload Excel file "%(filename)s" to table '
'"%(table_name)s" in database "%(db_name)s". '
"Error message: %(error_msg)s",
@ -417,7 +417,7 @@ class ExcelToDatabaseView(SimpleFormView):
return redirect("/exceltodatabaseview/form")
# Go back to welcome page / splash screen
message = __(
message = _(
'Excel file "%(excel_filename)s" uploaded to table "%(table_name)s" in '
'database "%(db_name)s"',
excel_filename=form.excel_file.data.filename,
@ -462,7 +462,7 @@ class ColumnarToDatabaseView(SimpleFormView):
]
if len(file_type) > 1:
message = __(
message = _(
"Multiple file extensions are not allowed for columnar uploads."
" Please make sure all files are of the same extension.",
)
@ -475,7 +475,7 @@ class ColumnarToDatabaseView(SimpleFormView):
}
if not schema_allows_file_upload(database, columnar_table.schema):
message = __(
message = _(
'Database "%(database_name)s" schema "%(schema_name)s" '
"is not allowed for columnar uploads. "
"Please contact your Superset Admin.",
@ -543,7 +543,7 @@ class ColumnarToDatabaseView(SimpleFormView):
db.session.commit()
except Exception as ex: # pylint: disable=broad-except
db.session.rollback()
message = __(
message = _(
'Unable to upload Columnar file "%(filename)s" to table '
'"%(table_name)s" in database "%(db_name)s". '
"Error message: %(error_msg)s",
@ -558,7 +558,7 @@ class ColumnarToDatabaseView(SimpleFormView):
return redirect("/columnartodatabaseview/form")
# Go back to welcome page / splash screen
message = __(
message = _(
'Columnar file "%(columnar_filename)s" uploaded to table "%(table_name)s" '
'in database "%(db_name)s"',
columnar_filename=[file.filename for file in form.columnar_file.data],

View File

@ -145,7 +145,7 @@ class TabStateView(BaseSupersetView):
user_id=get_user_id(),
# This is for backward compatibility
label=query_editor.get("name")
or query_editor.get("title", "Untitled Query"),
or query_editor.get("title", _("Untitled Query")),
active=True,
database_id=query_editor["dbId"],
schema=query_editor.get("schema"),

View File

@ -3224,7 +3224,7 @@ class PartitionViz(NVD3TimeSeriesViz):
groups = get_column_names(self.form_data.get("groupby"))
time_op = self.form_data.get("time_series_option", "not_time")
if not groups:
raise ValueError("Please choose at least one groupby")
raise ValueError(_("Please choose at least one groupby"))
if time_op == "not_time":
levels = self.levels_for("agg_sum", groups, df)
elif time_op in ["agg_sum", "agg_mean"]:

View File

@ -18,6 +18,7 @@
import json
import pandas as pd
from flask_babel import lazy_gettext as _
from numpy import True_
from pytest import raises
from sqlalchemy.orm.session import Session
@ -57,10 +58,10 @@ def test_pivot_df_no_cols_no_rows_single_metric():
)
assert (
pivoted.to_markdown()
== """
== f"""
| | ('SUM(num)',) |
|:-----------------|----------------:|
| ('Total (Sum)',) | 8.06797e+07 |
| ('{_("Total")} (Sum)',) | 8.06797e+07 |
""".strip()
)
@ -79,10 +80,10 @@ def test_pivot_df_no_cols_no_rows_single_metric():
)
assert (
pivoted.to_markdown()
== """
== f"""
| | ('SUM(num)',) |
|:-----------------|----------------:|
| ('Total (Sum)',) | 8.06797e+07 |
| ('{_("Total")} (Sum)',) | 8.06797e+07 |
""".strip()
)
@ -102,8 +103,8 @@ def test_pivot_df_no_cols_no_rows_single_metric():
)
assert (
pivoted.to_markdown()
== """
| | ('Total (Sum)',) |
== f"""
| | ('{_("Total")} (Sum)',) |
|:--------------|-------------------:|
| ('SUM(num)',) | 8.06797e+07 |
""".strip()
@ -124,10 +125,10 @@ def test_pivot_df_no_cols_no_rows_single_metric():
)
assert (
pivoted.to_markdown()
== """
== f"""
| | ('SUM(num)',) | ('Total (Sum)',) |
|:-----------------|----------------:|-------------------:|
| ('Total (Sum)',) | 8.06797e+07 | 8.06797e+07 |
| ('{_("Total")} (Sum)',) | 8.06797e+07 | 8.06797e+07 |
""".strip()
)
@ -162,10 +163,10 @@ def test_pivot_df_no_cols_no_rows_two_metrics():
)
assert (
pivoted.to_markdown()
== """
== f"""
| | ('SUM(num)',) | ('MAX(num)',) |
|:-----------------|----------------:|----------------:|
| ('Total (Sum)',) | 8.06797e+07 | 37296 |
| ('{_("Total")} (Sum)',) | 8.06797e+07 | 37296 |
""".strip()
)
@ -207,8 +208,8 @@ def test_pivot_df_no_cols_no_rows_two_metrics():
)
assert (
pivoted.to_markdown()
== """
| | ('Total (Sum)',) |
== f"""
| | ('{_("Total")} (Sum)',) |
|:--------------|-------------------:|
| ('SUM(num)',) | 8.06797e+07 |
| ('MAX(num)',) | 37296 |
@ -231,10 +232,10 @@ def test_pivot_df_no_cols_no_rows_two_metrics():
)
assert (
pivoted.to_markdown()
== """
| | ('SUM(num)',) | ('MAX(num)',) | ('Total (Sum)',) |
== f"""
| | ('SUM(num)',) | ('MAX(num)',) | ('{_("Total")} (Sum)',) |
|:-----------------|----------------:|----------------:|-------------------:|
| ('Total (Sum)',) | 8.06797e+07 | 37296 | 8.0717e+07 |
| ('{_("Total")} (Sum)',) | 8.06797e+07 | 37296 | 8.0717e+07 |
""".strip()
)
@ -297,10 +298,10 @@ def test_pivot_df_single_row_two_metrics():
)
assert (
pivoted.to_markdown()
== """
== f"""
| | ('SUM(num)', 'boy') | ('SUM(num)', 'girl') | ('MAX(num)', 'boy') | ('MAX(num)', 'girl') |
|:-----------------|----------------------:|-----------------------:|----------------------:|-----------------------:|
| ('Total (Sum)',) | 47123 | 118065 | 1280 | 2588 |
| ('{_("Total")} (Sum)',) | 47123 | 118065 | 1280 | 2588 |
""".strip()
)
@ -342,12 +343,12 @@ def test_pivot_df_single_row_two_metrics():
)
assert (
pivoted.to_markdown()
== """
| | ('SUM(num)',) | ('MAX(num)',) | ('Total (Sum)',) |
== f"""
| | ('SUM(num)',) | ('MAX(num)',) | ('{_("Total")} (Sum)',) |
|:-----------------|----------------:|----------------:|-------------------:|
| ('boy',) | 47123 | 1280 | 48403 |
| ('girl',) | 118065 | 2588 | 120653 |
| ('Total (Sum)',) | 165188 | 3868 | 169056 |
| ('{_("Total")} (Sum)',) | 165188 | 3868 | 169056 |
""".strip()
)
@ -366,8 +367,8 @@ def test_pivot_df_single_row_two_metrics():
)
assert (
pivoted.to_markdown()
== """
| | ('Total (Sum)',) |
== f"""
| | ('{_("Total")} (Sum)',) |
|:-------------------------|-------------------:|
| ('SUM(num)', 'boy') | 47123 |
| ('SUM(num)', 'girl') | 118065 |
@ -375,7 +376,7 @@ def test_pivot_df_single_row_two_metrics():
| ('MAX(num)', 'boy') | 1280 |
| ('MAX(num)', 'girl') | 2588 |
| ('MAX(num)', 'Subtotal') | 3868 |
| ('Total (Sum)', '') | 169056 |
| ('{_("Total")} (Sum)', '') | 169056 |
""".strip()
)
@ -394,8 +395,8 @@ def test_pivot_df_single_row_two_metrics():
)
assert (
pivoted.to_markdown()
== """
| | ('Total (Sum)',) |
== f"""
| | ('{_("Total")} (Sum)',) |
|:---------------------|-------------------:|
| ('boy', 'SUM(num)') | 47123 |
| ('boy', 'MAX(num)') | 1280 |
@ -403,7 +404,7 @@ def test_pivot_df_single_row_two_metrics():
| ('girl', 'SUM(num)') | 118065 |
| ('girl', 'MAX(num)') | 2588 |
| ('girl', 'Subtotal') | 120653 |
| ('Total (Sum)', '') | 169056 |
| ('{_("Total")} (Sum)', '') | 169056 |
""".strip()
)