apache / superset

Apache Superset is a Data Visualization and Data Exploration Platform
https://superset.apache.org/
Apache License 2.0
62.56k stars 13.78k forks source link

Chart Editor - Impossible to translate Time Grain dropdown contents #29478

Open goldjee opened 4 months ago

goldjee commented 4 months ago

Screenshot

image

Description

The editor for many chart types (like Line Chart, Scatter Plot etc) use "Time Grain" dropdown to adjust time granularity as depicted on the attached screenshot. It seems that the contents of this dropdown are not able to be translated as I haven't found corresponding lines in messages.po files.

Design input

dosubot[bot] commented 4 months ago

To make the contents of the "Time Grain" dropdown menu in the Chart Editor translatable, you need to use the t function from @superset-ui/core for each item in the dropdown. This function marks strings for translation.

Here is an example of how you can modify the code to make the dropdown items translatable:

import { t } from '@superset-ui/core';

// Example of dropdown items
const timeGrainOptions = [
  { value: 'PT1S', label: t('Second') },
  { value: 'PT1M', label: t('Minute') },
  { value: 'PT1H', label: t('Hour') },
  { value: 'P1D', label: t('Day') },
  { value: 'P1W', label: t('Week') },
  { value: 'P1M', label: t('Month') },
  { value: 'P3M', label: t('Quarter') },
  { value: 'P1Y', label: t('Year') },
];

// Use these options in your dropdown component

In this example, each label in the timeGrainOptions array is wrapped with the t function to ensure it is translatable. You would then use this timeGrainOptions array in your dropdown component.

Additionally, you can refer to the PluginFilterTimegrain component in the TimeGrainFilterPlugin.tsx file for a more comprehensive implementation. Here is a snippet from that file showing how translation is handled:

import {
  ensureIsArray,
  ExtraFormData,
  t,
  TimeGranularity,
  tn,
} from '@superset-ui/core';
import React, { useEffect, useMemo, useState } from 'react';
import { Select } from 'src/components';
import { FormItemProps } from 'antd/lib/form';
import { FilterPluginStyle, StyledFormItem, StatusMessage } from '../common';
import { PluginFilterTimeGrainProps } from './types';

export default function PluginFilterTimegrain(
  props: PluginFilterTimeGrainProps,
) {
  const {
    data,
    formData,
    height,
    width,
    setDataMask,
    setHoveredFilter,
    unsetHoveredFilter,
    setFocusedFilter,
    unsetFocusedFilter,
    setFilterActive,
    filterState,
    inputRef,
  } = props;
  const { defaultValue } = formData;

  const [value, setValue] = useState<string[]>(defaultValue ?? []);
  const durationMap = useMemo(
    () =>
      data.reduce(
        (agg, { duration, name }: { duration: string; name: string }) => ({
          ...agg,
          [duration]: name,
        }),
        {} as { [key in string]: string },
      ),
    [JSON.stringify(data)],
  );

  const handleChange = (values: string[] | string | undefined | null) => {
    const resultValue: string[] = ensureIsArray<string>(values);
    const [timeGrain] = resultValue;
    const label = timeGrain ? durationMap[timeGrain] : undefined;

    const extraFormData: ExtraFormData = {};
    if (timeGrain) {
      extraFormData.time_grain_sqla = timeGrain as TimeGranularity;
    }
    setValue(resultValue);
    setDataMask({
      extraFormData,
      filterState: {
        label,
        value: resultValue.length ? resultValue : null,
      },
    });
  };

  useEffect(() => {
    handleChange(defaultValue ?? []);
  }, [JSON.stringify(defaultValue)]);

  useEffect(() => {
    handleChange(filterState.value ?? []);
  }, [JSON.stringify(filterState.value)]);

  const placeholderText =
    (data || []).length === 0
      ? t('No data')
      : tn('%s option', '%s options', data.length, data.length);

  const formItemData: FormItemProps = {};
  if (filterState.validateMessage) {
    formItemData.extra = (
      <StatusMessage status={filterState.validateStatus}>
        {filterState.validateMessage}
      </StatusMessage>
    );
  }

  const options = (data || []).map(
    (row: { name: string; duration: string }) => {
      const { name, duration } = row;
      return {
        label: name,
        value: duration,
      };
    },
  );

  return (
    <FilterPluginStyle height={height} width={width}>
      <StyledFormItem
        validateStatus={filterState.validateStatus}
        {...formItemData}
      >
        <Select
          allowClear
          value={value}
          placeholder={placeholderText}
          onChange={handleChange}
          onBlur={unsetFocusedFilter}
          onFocus={setFocusedFilter}
          onMouseEnter={setHoveredFilter}
          onMouseLeave={unsetHoveredFilter}
          ref={inputRef}
          options={options}
          onDropdownVisibleChange={setFilterActive}
        />
      </StyledFormItem>
    </FilterPluginStyle>
  );
}

In this component, the t function is used to translate the placeholder text and other strings, ensuring that the dropdown menu contents are translatable [1][2].

To continue talking to Dosu, mention @dosu.