or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

api

features

charts

charts.mdconditional-formatting.mdvisualizations.md
authorization.mdchangesets.mdcharts-as-code.mdcompiler.mddashboards.mddbt.mdee-features.mdformatting.mdparameters.mdpivot.mdprojects-spaces.mdsql-runner.mdtemplating.mdwarehouse.md
index.md
tile.json

i18n.mddocs/api/utilities/specialized/

Internationalization (i18n) Utilities

Utilities for managing translations and localization of Charts-as-Code and Dashboards-as-Code. These utilities enable extracting translatable content, creating language maps, and merging translations back into content definitions.

Core Concepts

The i18n system for Charts and Dashboards as Code:

  1. Extract translatable fields (names, descriptions, labels) from content
  2. Generate language maps containing only translatable strings
  3. Merge translations back into the original content structure

Capabilities

Chart Internationalization

Manages translations for Chart-as-Code content including chart names, descriptions, axis labels, series names, and reference line labels.

/**
 * Handles internationalization for Chart-as-Code content
 * Extracts translatable fields and merges translations back
 */
class ChartAsCodeInternalization {
  /**
   * Extract translatable content into a language map
   * @param chartAsCode - Chart-as-Code content to extract from
   * @returns Language map with chart slug as key and translatable fields
   */
  getLanguageMap(chartAsCode: ChartAsCode): ChartAsCodeLanguageMap;

  /**
   * Merge translated content back into chart definition
   * @param internalizationMap - Translated content map
   * @param content - Original chart content
   * @returns Updated chart with merged translations
   */
  merge(
    internalizationMap: ChartAsCodeLanguageMap['chart'][string],
    content: ChartAsCode
  ): ChartAsCode;
}

/**
 * Language map structure for charts
 * Contains translatable fields organized by chart slug
 */
type ChartAsCodeLanguageMap = {
  chart: {
    [slug: string]: {
      name?: string;
      description?: string;
      chartConfig?: {
        type: ChartType;
        config?: {
          // Cartesian chart labels
          eChartsConfig?: {
            xAxis?: Array<{ name?: string }>;
            yAxis?: Array<{ name?: string }>;
            series?: Array<{
              name?: string;
              markLine?: {
                data?: Array<{ name?: string }>;
              };
            }>;
          };
          // Pie chart labels
          groupLabelOverrides?: Record<string, string>;
          // Funnel chart labels
          labelOverrides?: Record<string, string>;
          // Big number labels
          label?: string;
          comparisonLabel?: string;
          // Table column names
          columns?: Record<string, { name: string }>;
        };
      };
    };
  };
};

Usage Example:

import {
  ChartAsCodeInternalization,
  type ChartAsCodeLanguageMap,
  type ChartAsCode,
} from '@lightdash/common';

const internalization = new ChartAsCodeInternalization();

// Extract translatable content from a chart
const chart: ChartAsCode = {
  slug: 'revenue-trend',
  name: 'Revenue Trend',
  description: 'Monthly revenue analysis',
  chartConfig: {
    type: 'cartesian',
    config: {
      eChartsConfig: {
        xAxis: [{ name: 'Month' }],
        yAxis: [{ name: 'Revenue' }],
        series: [{ name: 'Total Revenue' }],
      },
    },
  },
  // ... other properties
};

// Get language map for translation
const languageMap = internalization.getLanguageMap(chart);
/* Returns:
{
  chart: {
    'revenue-trend': {
      name: 'Revenue Trend',
      description: 'Monthly revenue analysis',
      chartConfig: {
        config: {
          eChartsConfig: {
            xAxis: [{ name: 'Month' }],
            yAxis: [{ name: 'Revenue' }],
            series: [{ name: 'Total Revenue' }]
          }
        }
      }
    }
  }
}
*/

// After translation, merge back
const translatedMap = {
  name: 'Tendencia de Ingresos',
  description: 'Análisis mensual de ingresos',
  chartConfig: {
    config: {
      eChartsConfig: {
        xAxis: [{ name: 'Mes' }],
        yAxis: [{ name: 'Ingresos' }],
        series: [{ name: 'Ingresos Totales' }],
      },
    },
  },
};

const translatedChart = internalization.merge(translatedMap, chart);
// Returns chart with Spanish translations merged in

Dashboard Internationalization

Manages translations for Dashboard-as-Code content including dashboard names, descriptions, and tile titles.

/**
 * Handles internationalization for Dashboard-as-Code content
 * Extracts translatable fields and merges translations back
 */
class DashboardAsCodeInternalization {
  /**
   * Extract translatable content into a language map
   * @param dashboardAsCode - Dashboard-as-Code content to extract from
   * @returns Language map with dashboard slug as key and translatable fields
   */
  getLanguageMap(dashboardAsCode: DashboardAsCode): DashboardAsCodeLanguageMap;

  /**
   * Merge translated content back into dashboard definition
   * @param internalizationMap - Translated content map
   * @param content - Original dashboard content
   * @returns Updated dashboard with merged translations
   */
  merge(
    internalizationMap: DashboardAsCodeLanguageMap['dashboard'][string],
    content: DashboardAsCode
  ): DashboardAsCode;
}

/**
 * Language map structure for dashboards
 * Contains translatable fields organized by dashboard slug
 */
type DashboardAsCodeLanguageMap = {
  dashboard: {
    [slug: string]: {
      name?: string;
      description?: string;
      tiles?: Array<{
        properties?: {
          title?: string;
          chartName?: string;
          content?: string; // Markdown content
        };
      }>;
    };
  };
};

Usage Example:

import {
  DashboardAsCodeInternalization,
  type DashboardAsCodeLanguageMap,
  type DashboardAsCode,
} from '@lightdash/common';

const internalization = new DashboardAsCodeInternalization();

// Extract translatable content from a dashboard
const dashboard: DashboardAsCode = {
  slug: 'sales-overview',
  name: 'Sales Overview',
  description: 'Key sales metrics and trends',
  tiles: [
    {
      type: 'saved_chart',
      properties: {
        title: 'Revenue Chart',
        chartName: 'revenue-trend',
      },
    },
    {
      type: 'markdown',
      properties: {
        title: 'Summary',
        content: '## Overview\nSales performance summary',
      },
    },
  ],
  // ... other properties
};

// Get language map for translation
const languageMap = internalization.getLanguageMap(dashboard);
/* Returns:
{
  dashboard: {
    'sales-overview': {
      name: 'Sales Overview',
      description: 'Key sales metrics and trends',
      tiles: [
        { properties: { title: 'Revenue Chart', chartName: 'revenue-trend' } },
        { properties: { title: 'Summary', content: '## Overview\nSales performance summary' } }
      ]
    }
  }
}
*/

// After translation, merge back
const translatedMap = {
  name: 'Resumen de Ventas',
  description: 'Métricas clave y tendencias de ventas',
  tiles: [
    { properties: { title: 'Gráfico de Ingresos' } },
    { properties: { title: 'Resumen', content: '## Resumen\nResumen del rendimiento de ventas' } },
  ],
};

const translatedDashboard = internalization.merge(translatedMap, dashboard);
// Returns dashboard with Spanish translations merged in

Merge Utility

Deep merge utility for combining translation maps with original content.

/**
 * Merge translated content into original structure
 * Only updates fields that exist in the translation map
 * Recursively merges nested objects
 * @param left - Original content object
 * @param right - Translation map with updates
 * @returns Merged object with translations applied
 */
function mergeExisting(left: any, right: any): any;

Usage Example:

import { mergeExisting } from '@lightdash/common';

const original = {
  name: 'Dashboard',
  description: 'Original description',
  settings: {
    theme: 'light',
    title: 'Original Title',
  },
  otherField: 'preserved',
};

const translations = {
  name: 'Tableau de Bord',
  description: 'Description traduite',
  settings: {
    title: 'Titre Traduit',
    // theme not included - will be preserved
  },
  // otherField not included - will be preserved
};

const result = mergeExisting(original, translations);
/* Returns:
{
  name: 'Tableau de Bord',
  description: 'Description traduite',
  settings: {
    theme: 'light',           // preserved
    title: 'Titre Traduit'    // updated
  },
  otherField: 'preserved'     // preserved
}
*/

Combined Language Map Type

/**
 * Combined type for all language maps
 * Used when managing translations for multiple content types
 */
type LanguageMap = Partial<
  ChartAsCodeLanguageMap & DashboardAsCodeLanguageMap
>;

Translation Workflow

1. Extract Translatable Content

import {
  ChartAsCodeInternalization,
  DashboardAsCodeInternalization,
} from '@lightdash/common';

// Initialize internalization handlers
const chartI18n = new ChartAsCodeInternalization();
const dashboardI18n = new DashboardAsCodeInternalization();

// Extract from multiple charts and dashboards
const languageMap = {
  ...chartI18n.getLanguageMap(chart1),
  ...chartI18n.getLanguageMap(chart2),
  ...dashboardI18n.getLanguageMap(dashboard1),
};

// Export for translation
console.log(JSON.stringify(languageMap, null, 2));

2. Translate Content

Send the language map to translators or translation service. The structure makes it easy to identify what needs translation.

3. Merge Translations

import { mergeExisting } from '@lightdash/common';

// Load translated language map
const translatedMap = loadTranslations('es');

// Merge back into content
const translatedChart = chartI18n.merge(
  translatedMap.chart['chart-slug'],
  originalChart
);

const translatedDashboard = dashboardI18n.merge(
  translatedMap.dashboard['dashboard-slug'],
  originalDashboard
);

Chart Types Supported

The Chart i18n system handles all chart types:

  • Cartesian: Axis names, series names, reference line labels
  • Pie: Group label overrides
  • Funnel: Label overrides
  • Big Number: Label and comparison label
  • Table: Column name overrides
  • Custom: Custom spec translations (if applicable)

Best Practices

  1. Keep slugs untranslated: Slugs are identifiers and should remain in English
  2. Preserve structure: Don't add or remove fields in translation maps
  3. Use partial maps: Only include fields that need translation
  4. Validate after merge: Ensure merged content is valid using schemas
  5. Version translations: Track translation versions alongside content versions

Related

  • Charts-as-Code - Content-as-code system documentation
  • Dashboard Types - Dashboard and chart type definitions
  • Validation - Schema validation utilities