feat: content localization for dashboards, charts, and filters#37790
feat: content localization for dashboards, charts, and filters#37790YuriyKrasilnikov wants to merge 78 commits intoapache:masterfrom
Conversation
Add infrastructure for localizing user-generated content (dashboard
titles, chart names, filter labels) based on viewer's UI language.
Changes:
- Add ENABLE_CONTENT_LOCALIZATION feature flag (default: False)
- Add translations JSON column to dashboards and slices tables
- Add translations to export_fields for import/export support
- Add unit tests for feature flag and migration
The translations column stores content translations in JSON format:
{
"field_name": {
"locale": "translated_value"
}
}
Implement LocalizableMixin to support localization of user-generated
content (dashboard titles, chart names) based on viewer's locale.
- Add LocalizableMixin with get_localized(), set_translation(),
get_available_locales() methods
- Integrate mixin into Dashboard and Slice models
- Add 25 unit tests for mixin and model localization
- Fallback chain: exact locale -> base language -> original value
Example translations format:
{
"dashboard_title": {"de": "Verkaufs-Dashboard", "fr": "Tableau de bord"}
}
Add locale detection and content localization to API responses: - Locale detection from session, Accept-Language header, or config default - Dashboard schema returns localized title, description, and native filter names - Chart schema returns localized slice_name and description - Fallback chain: exact locale → base language → original value All changes gated behind ENABLE_CONTENT_LOCALIZATION feature flag.
Add unit tests verifying translations field works correctly in dashboard and chart export/import operations: - Export includes translations when present - Export omits translations when None - Import preserves translations - Import without translations works (backward compatible) - Roundtrip export→import preserves translations exactly Update shared test fixtures with translations examples (de, fr).
Add Jinja template function for locale-aware SQL queries:
- SELECT * FROM table WHERE language = '{{ current_user_locale() }}'
- {% if current_user_locale() == 'de' %}...{% endif %}
Returns user's locale string (never None, falls back to config default).
Adds locale to cache key by default for correct per-locale caching.
Add 'translations' to OVERWRITE_INSPECT_FIELDS for concurrent editing. When two users edit translations simultaneously, the second user sees a diff dialog showing old vs new translations before overwriting. Refactored getOverwriteItems to properly serialize object values, enabling correct comparison for JSON object fields like translations.
Add REST API endpoint for TranslationEditor UI to query available locales from LANGUAGES config: - LocalizationRestApi with GET /available_locales - LocaleSchema, AvailableLocalesResponseSchema for response - get_available_locales_data() pure function for testability - Locales sorted by code, includes default_locale - 12 unit tests covering all business logic
Add ?include_translations query parameter to dashboard and chart GET APIs: - Default mode: returns localized field values, excludes translations dict - Editor mode (?include_translations=true): returns original values + full translations dict Add feature flag validation on PUT schemas: - DashboardPutSchema and ChartPutSchema accept translations field - Rejects translations when ENABLE_CONTENT_LOCALIZATION=False with 400 error - Validates via @validates_schema decorator in schema layer Tests: 24 new tests (14 for include_translations, 10 for feature flag PUT)
Strip HTML tags from translation values on PUT to prevent XSS attacks. Translations are stored as plain text; React escapes on render. - Add sanitization module with sanitize_translation_value() and sanitize_translations() using nh3 library - Add @post_load sanitization in DashboardPutSchema and ChartPutSchema - Add 23 unit tests covering XSS vectors and schema integration - Fix mypy type annotation in api.py
Validate translations dict before storage: - Field names: string, 1-50 chars - Locale codes: BCP 47 (pt-BR) and POSIX (pt_BR) formats - Translation values: string, max 10000 chars - Total unique locales: max 50 per entity - JSON size: max 1MB Config constants added: - CONTENT_LOCALIZATION_MAX_LOCALES - CONTENT_LOCALIZATION_MAX_TEXT_LENGTH - CONTENT_LOCALIZATION_MAX_FIELD_LENGTH - CONTENT_LOCALIZATION_MAX_JSON_SIZE Validation runs in @validates_schema after feature flag check, before XSS sanitization.
Implements priority chain for chart names in dashboard context: 1. Override translation (json_metadata.slice_name_overrides[uuid].translations) 2. Override name (position_json.meta.sliceNameOverride) 3. Chart translation (chart.translations["slice_name"]) 4. Chart original name (chart.slice_name) Changes: - Add slice_name_utils.py with get_localized_chart_name() and localize_chart_names() - Add slice_name_overrides field to DashboardJSONMetadataSchema - Integrate _localize_chart_names() in DashboardGetResponseSchema._apply_localization() - Add 21 unit tests for utility functions - Add 3 integration tests for schema localization
- Add new Localization.ts with core types: - FieldTranslations, Translations, LocalizableEntity - LocaleInfo, AvailableLocalesResponse - Update Chart.ts: add translations, available_locales to Chart and Slice - Update Dashboard.ts: add translations, available_locales to Dashboard - Update dashboard/types.ts: add translations to DashboardInfo and Slice
- Add GlobalOutlined icon to AntdEnhanced.tsx - TranslationButton: button with globe icon and translation count, opens translation editor modal - TranslationField: single locale row with label, input, remove button, used inside TranslationEditorModal - 10 unit tests (5 TranslationButton + 5 TranslationField) - index.ts re-exports for TranslationEditor module
- TranslationEditorModal: modal for editing per-field translations with add/remove language, edit values, save/cancel workflow - Per-field sections with original value, locale inputs, language select - Strips empty values on save, deep-copies on open, discards on cancel - 9 unit tests covering render, edit, add, remove, save, cancel - Re-export TranslationEditorModal and TranslatableField from index.ts
…e aria-labels Replace data-test attributes with aria-label for accessibility. Switch tests from getByTestId to role-based selectors (getByRole, getByDisplayValue).
…Modal Add EnableContentLocalization to FeatureFlag enum. Wire TranslationButton and TranslationEditorModal into the General Information section of PropertiesModal, gated by the feature flag. Fetch available locales from /api/v1/localization/available_locales. Pass ?include_translations=true on dashboard GET for editor mode. Include translations in PUT payload on save.
Add content localization support to the Explore chart properties modal, gated by EnableContentLocalization feature flag. When enabled: - Fetch chart with ?include_translations=true to get original values - Fetch available locales from /api/v1/localization/available_locales - Show TranslationButton in General settings section - TranslationEditorModal as sibling to StandardModal via Fragment - Include translations in PUT payload on save - Translatable fields: slice_name, description Tests: 4 new (button hidden/visible, opens modal, save payload) Fix pre-existing flaky test: toBeVisible → toBeInTheDocument for modal header (ant-zoom-appear-prepare animation timing)
Add content localization support to native filters in FiltersConfigModal. When ENABLE_CONTENT_LOCALIZATION flag is on, a TranslationButton appears next to the filter name input, opening TranslationEditorModal for managing filter name translations. - Add translations field to Filter type in @superset-ui/core - Add translations field to NativeFiltersFormItem interface - Pass translations through transformFormInput in filterTransformer - Register translations as hidden FormItem so form.validateFields() includes it in save pipeline - Fetch available locales from /api/v1/localization/available_locales - 4 new tests: button hidden/visible by flag, modal open, save payload
Add dynamic locale switching for embedded dashboards via the Switchboard API, following the same pattern as setThemeMode. SDK: setLocale(locale) emits 'setLocale' event to iframe. Frontend: Switchboard handler fetches language pack, reconfigures translation singleton, updates dayjs locale, and reloads page to re-fetch server-side localized content.
- Add docs/docs/configuration/content-localization.mdx with full user guide - Add Content Localization section to UPDATING.md - Add @docs annotation to ENABLE_CONTENT_LOCALIZATION feature flag - Add docs link to feature-flags.json entry
Add Playwright E2E tests covering dashboard translation CRUD, locale switching, available locales endpoint, and UI integration with the TranslationEditor modal. New files: - playwright/helpers/api/dashboard.ts — Dashboard API CRUD helpers - playwright/helpers/api/localization.ts — Localization API helper - playwright/components/modals/TranslationEditorModal.ts — Modal POM - playwright/tests/experimental/localization/dashboard-translations.spec.ts Changes: - DashboardPage: add enterEditMode() and clickEditProperties() methods - modals/index.ts: export TranslationEditorModal - Fix TS2352 in PropertiesModal test mocks (double assertion via unknown)
- Fix locale detection priority to match implementation: explicit param > session locale > Accept-Language > config default - Fix dashboard translation UI flow: requires edit mode first - Fix API example: localization driven by session locale, not Accept-Language
…cher Replace the modal-based translation editing workflow with an inline locale switcher dropdown rendered as Input suffix. Users can now switch between DEFAULT text and per-locale translations directly in the form field without opening a separate modal. - Add LocaleSwitcher component with antd Dropdown - Add utils.ts with deepCopyTranslations, stripEmptyValues, countFieldTranslations - Integrate into Dashboard PropertiesModal, Chart PropertiesModal, FiltersConfigForm - Delete TranslationEditorModal, TranslationButton, TranslationField components - Update unit tests and Playwright E2E tests for new selectors
…ntent-localization # Conflicts: # UPDATING.md # superset-frontend/src/dashboard/types.ts
…docs Remove redundant text label (e.g. "DE") from LocaleSwitcher trigger — show only flag/globe icon + badge + caret. Update content localization docs to reflect inline LocaleSwitcher replacing the old Translations button.
… Table charts - Sankey: use getLocalizedMetricLabel for tooltip metric display - Pivot Table: merge localizedMetricLabelMap into verboseMap for metric display - Add unit tests for metric localization in both charts
Add buildLocalizedColumnLabelMap for groupbyRows and groupbyColumns, merge into enhancedVerboseMap alongside metric labels. Column names display as localized headers via namesMapping → displayHeaderCell.
- Timeseries: "Total" in tooltip total row - MixedTimeseries: "zoom area"/"restore zoom" in toolbox - BoxPlot: tooltip stat labels (Max, Mean, Median, Quartiles, etc.) - BigNumber POP: comparison period titles (Range, Year, Month, Week)
- BigNumber Trendline: use displayLabel instead of raw metricName - Heatmap: use localizedMetricLabel instead of colnames[2] - Radar: pass localizedMetricLabelMap to renderNormalizedTooltip
…chart - Import buildLocalizedMetricLabelMap for both primary and secondary metrics - Apply localized labels in tooltip seriesName and legend formatter - Add unit test verifying localized metric labels in legend
…and Tree charts - BoxPlot: localize metric labels in category names (xAxis + tooltip) - Graph: use getLocalizedMetricLabel for tooltip metric display - Tree: use getLocalizedMetricLabel for tooltip metric display - BoxPlot: wrap zoom area/restore zoom strings with t() - Add unit tests for all three charts
Add TranslatableTextControl component that extends TextControl with inline translation editing via LocaleSwitcher dropdown. When the ENABLE_CONTENT_LOCALIZATION feature flag is active, users can provide per-locale translations for text form fields directly in the control panel. Add getLocalizedFormDataValue utility that resolves a translated value from formData.translations for a given field and locale, with base language fallback (e.g. "de-AT" falls back to "de"). Update axis title controls (x_axis_title, y_axis_title) to use TranslatableTextControl in shared titleControls, Histogram, and Timeseries Bar control panels. Resolve localized axis titles in Timeseries transformProps so that charts render translated xAxis/yAxis names based on the viewer's locale.
Resolve localized axis titles (x, primary y, secondary y) in MixedTimeseries transformProps via getLocalizedFormDataValue, matching the pattern established in Timeseries charts. Update yAxisTitleSecondary control from TextControl to TranslatableTextControl so users can provide per-locale translations for the secondary y-axis title.
Resolve localized axis titles in Bubble transformProps via getLocalizedFormDataValue for x_axis_label and y_axis_label controls. Update both controls from TextControl to TranslatableTextControl so users can provide per-locale translations for axis titles.
…d Histogram charts Resolve translated axis titles using getLocalizedFormDataValue with locale-based fallback (e.g. de-AT → de) in BoxPlot, Gantt, and Histogram transformProps. Localized values replace raw formData strings in ECharts axis name config and padding offset calculations. All three control panels already use TranslatableTextControl for x_axis_title / y_axis_title fields.
Localize all user-configurable text labels in the Waterfall chart: axis titles (xAxisLabel, yAxisLabel) and series labels (increaseLabel, decreaseLabel, totalLabel). The totalMark data sentinel is kept separate from the localized display label to avoid corrupting the data pipeline while showing translated text in legend, tooltip, and x-axis formatter. Control panel: 5 controls changed from TextControl to TranslatableTextControl (x_axis_label, y_axis_label, increase_label, decrease_label, total_label).
…gNumber charts Localize user-configurable text in all three BigNumber variants: - BigNumberTotal: subtitle with legacy subheader fallback - BigNumberWithTrendline: subtitle + comparison suffix (shown in formatted subheader as "percentChange compareSuffix") - BigNumberPeriodOverPeriod: subtitle Control panel: subtitle shared control changed from TextControl to TranslatableTextControl (affects all 3 variants), compare_suffix in BigNumberWithTrendline also changed to TranslatableTextControl.
Gantt chart tooltip displayed raw metric labels (`getMetricLabel`) for tooltip metrics. When a metric has per-locale translations, the tooltip now shows the localized label while preserving the original label as the data key for column lookup in `dimensionNames`. Uses `buildLocalizedMetricLabelMap` (same pattern as Timeseries) to build an original→localized map, then resolves display labels in the tooltip formatter via `localizedMetricLabelMap[label] ?? label`. Column labels in the same tooltip are unaffected — they are datasource metadata and do not require content localization.
…d MixedTimeseries charts
Add support for localizing user-defined annotation layer names in chart
legend, tooltip, and annotation labels (Formula, Interval, Event, and
Timeseries annotation types).
Render pipeline:
- Add `translations` field to `BaseAnnotationLayer` type for per-locale
name storage (e.g., `{ name: { de: "Umsatzziel" } }`)
- Create `getLocalizedAnnotationName()` utility that resolves localized
names with exact locale match and base-language fallback, delegating
to `getLocalizedFormDataValue` for DRY locale resolution
- Update all four annotation transformers (`transformFormulaAnnotation`,
`transformIntervalAnnotation`, `transformEventAnnotation`,
`transformTimeseriesAnnotation`) to accept optional `localizedName`
parameter: `series.name` uses localized text for display while
`series.id` preserves original name for stable ECharts animation keys
- Update `extractAnnotationLabels()` to accept `locale` parameter
- Update Timeseries and MixedTimeseries `transformProps` to pre-resolve
localized annotation names and pass them through the render pipeline
- Replace O(n) tooltip annotation filtering with O(1) Set lookup using
localized names for correct matching
Editor UI:
- Create `TranslatableNameField` component for annotation layer name
editing with LocaleSwitcher and TranslationInput support
- Two-component architecture: outer gate checks feature flag (renders
plain TextControl when off, no Redux dependency), inner component
uses hooks for locale state management
- Integrate into `AnnotationLayer.tsx`: add `translations` to state,
props, and `annotationFields` for persistence in chart params
…ions to POST/Copy schemas Extract duplicated translations validation and XSS sanitization from ChartPutSchema and DashboardPutSchema into a shared TranslatableSchemaMixin. Add translations support to ChartPostSchema, DashboardPostSchema, and DashboardCopySchema so that translations can be provided at creation and copy time, not only on update. All five schemas now inherit from TranslatableSchemaMixin which provides: - translations Dict field (optional, nullable) - @validates_schema: feature flag check + structure validation - @post_load: XSS sanitization of translation values
…odal Add locale switching and translation persistence to the chart save/overwrite workflow. When content localization is enabled, users can enter translations for the chart name directly in the Save modal. Components: - TranslatableSliceNameField: functional component rendering Input with LocaleSwitcher suffix (feature flag OFF → plain Input, no overhead) - SaveModal: loads existing translations via ?include_translations=true, passes translations through updateSlice/createSlice to the API - getSlicePayload: accepts optional translations parameter Tests: 8 new behavioral tests (5 SaveModal + 3 getSlicePayload), 1 existing test updated for new createSlice signature.
When saving a dashboard via inline edit (overwriteDashboard), translations stored in Redux are now included in the PUT payload. This ensures that translations edited via PropertiesModal in apply-only mode are not lost when the user subsequently saves the dashboard. Changes: - Add translations to DashboardSaveData interface and overwrite PUT body - Bridge PropertiesModal translations through handleOnPropertiesChange to Redux via dashboardInfoChanged - Add PropertiesChanges.translations field so Header receives them - Add dashboardInfo.translations to overwriteDashboard dependency array When translations are undefined (normal load without PropertiesModal), they are omitted from the PUT payload, so the backend preserves existing DB values.
When a dashboard is copied via "Save As", translations are now carried through the full data flow: SaveModal passes translations from dashboardInfo, saveDashboardRequest includes them in the POST payload, and the backend DashboardDAO.copy_dashboard stores them on the new dashboard. If the frontend does not provide translations (e.g. PropertiesModal was never opened), the backend defaults to copying translations from the original dashboard, consistent with how params are copied.
superset-frontend/packages/superset-ui-core/src/query/getLocalizedFormDataValue.ts
Show resolved
Hide resolved
superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
Show resolved
Hide resolved
superset-frontend/src/components/TranslationEditor/LocaleSwitcher.tsx
Outdated
Show resolved
Hide resolved
...ashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/FiltersConfigForm.tsx
Show resolved
Hide resolved
superset-frontend/src/explore/components/controls/TranslatableTextControl/index.tsx
Outdated
Show resolved
Hide resolved
Metric and column label localization is handled on the frontend in transformProps, not in ChartEntityResponseSchema. Remove tests that incorrectly expected backend schema to localize form_data metric/column labels.
…on tests Add missing getLocalizedMetricLabel and getLocalizedFormDataValue to BigNumber test mocks. Fix ChartProps generic type in chart plugin tests to use explicit SqlaFormData, add as-const to aggregate literals, type tooltip access in Sankey, accept null in addToasts, and add required formData fields in TranslatableTextControl tests.
- TranslatableTextControl: replace broken prevValueRef with useEffect sync, matching BoundsControl pattern (external value changes ignored) - LocaleSwitcher: keyboard toggle now uses handleOpenChange instead of direct setState, so onDropdownOpenChange fires for keyboard users - LocaleProvider: use finally instead of catch-only for setIsLoading, preventing stuck loading state on same-locale setLocale calls - LocaleController: store initializeLocale promise in pendingLocaleChange to prevent race condition with early setLocale calls - schema_mixin: allow translations=null to bypass feature flag check, since null means "no translations" and needs no validation
superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
Show resolved
Hide resolved
...ntend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelectPopoverTitle.tsx
Show resolved
Hide resolved
Response to CodeAnt AI reviews (Feb 17 + Feb 18)Verified all 16 findings against the actual code. 5 were real bugs and have been fixed in Fixed (5) — commit
|
…ntent-localization # Conflicts: # UPDATING.md # superset-frontend/playwright/helpers/api/chart.ts # superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts # superset-frontend/plugins/plugin-chart-echarts/test/MixedTimeseries/transformProps.test.ts # superset-frontend/src/explore/components/PropertiesModal/index.tsx
…Explore Add LocaleSwitcher to all inline title editors using shared hooks: - useAvailableLocales: cached fetch of available locales - useTranslatableTitle: locale-switching logic for inline editors Inline editors now support content translations: - sliceNameOverride on dashboard chart cards - Dashboard tab names - Chart title in Explore header - Dashboard title in edit mode
superset-frontend/src/components/TranslationEditor/useTranslatableTitle.ts
Show resolved
Hide resolved
…eSwitcher without existing translations Slice.data property did not include the translations field, so the /api/v1/explore/ endpoint never returned translations for charts. This prevented the inline LocaleSwitcher from rendering in Explore. Also remove the translations !== undefined guard from useTranslatableTitle so the LocaleSwitcher renders even for entities that have no translations yet, allowing users to add the first translation. Update content localization docs to cover all translatable content types including inline editors, axis titles, metric/column labels, annotation names, and the initialLocale param in Embedded SDK.
|
@mistercrunch @michael-s-molina @betodealmeida @eschutho @sadpandajoe This PR implements the complete Content Localization feature (SIP-201 #37789) — enabling translation of user-created content (chart names, dashboard titles, metric labels, axis titles, annotations) without requiring dashboard duplication per language. The feature is fully gated behind the Current stateThe implementation spans ~20k lines across 220 files (68 commits). This volume is not practical to review as a single PR. Proposed approach: 7 stacked PRsI propose splitting this into a series of independently reviewable PRs, each delivering standalone value:
PRs 3a–3d and 4 have no dependencies on each other and can be reviewed and merged in any order once PR 2 is in. Questions
The complete implementation is functional on this branch for early evaluation if needed. |
SUMMARY
Adds content localization to Apache Superset. Users can translate dashboard titles, chart names, descriptions, metric labels, axis titles, annotation names, and more directly in the UI. Viewers see content in their language automatically.
Backend:
translationsJSON column on Dashboard and Slice models viaLocalizableMixin?include_translations=trueGET /api/v1/localization/available_localesendpoint{{ current_user_locale() }}Jinja macro for locale-aware SQL queriesSlice.dataproperty includestranslationsfor/api/v1/explore/endpointFrontend — Properties Modals (globe icon next to text fields):
Frontend — Inline Editors (globe icon next to titles in edit mode):
useAvailableLocales(cached locale fetch),useTranslatableTitle(locale-switching logic)Frontend — Chart Controls in Explore (globe icon inside control inputs):
TranslatableTextControlon Timeseries, MixedTimeseries, Bubble, BoxPlot, Gantt, Histogram, WaterfallFrontend — Chart Visualizations (automatic):
Translated metric labels display in 20 chart types: Table, Pivot Table, Pie, Funnel, Gauge, Radar, Treemap, Sunburst, Bubble, Heatmap, Graph, Tree, Sankey, BoxPlot, Gantt, BigNumber (Total, Trendline, PeriodOverPeriod), Timeseries, MixedTimeseries. Column labels in Table and Pivot Table.
Embedded SDK:
initialLocaleparameter inembedDashboard()for setting locale at initializationsetLocale(locale)method for dynamic locale switchingGated behind
ENABLE_CONTENT_LOCALIZATIONfeature flag (default: off).Related SIP: #37789
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
BEFORE: No content localization — all dashboard/chart/filter names are single-language.
AFTER: LocaleSwitcher inline dropdown appears next to translatable fields in edit mode:
TESTING INSTRUCTIONS
FEATURE_FLAGS = {"ENABLE_CONTENT_LOCALIZATION": True}LANGUAGESdictsuperset db upgradeUnit tests:
Backend tests:
ADDITIONAL INFORMATION
ENABLE_CONTENT_LOCALIZATIONdashboardsandslices— near-instant on PostgreSQL (metadata-only), under 1s on MySQL. No data backfill or table scan. Zero downtime — existing rows get NULL (= no translations).