Locale and date formats

Show dates in your users' language, and replace a label Intl cannot produce.

Set locale to a BCP 47 tag and every date label is formatted by Intl.DateTimeFormat, with no bundle to install and no locale file to import. With no tag the chart prints its built-in English labels.

Show dates in another language

locale takes the tag your app already uses. Nothing else in the setup changes.

ProjectChart.tsx
import { useState } from 'react';
import { ReactGanttChart, type Task } from '@jaeungkim/gantt-chart';
import '@jaeungkim/gantt-chart/style.css';

const initialTasks: Task[] = [
  { id: 'design', name: 'Design', parentId: null, sequence: '1',
    startDate: '2026-09-01', endDate: '2026-09-12' },
  { id: 'build', name: 'Build', parentId: null, sequence: '2',
    startDate: '2026-09-15', endDate: '2026-10-10' },
];

export function ProjectChart() {
  const [tasks, setTasks] = useState(initialTasks);

  return (
    <ReactGanttChart
      tasks={tasks}
      onTasksChange={setTasks}
      locale="ko-KR"
      showTaskList
      height={420}
    />
  );
}

The tag reaches every date the chart writes. Every formatter is built with timeZone: 'UTC', so a label never shifts into the viewer's time zone.

The task list needs no locale, because it renders task.name from your data.

An editable Start or End never reaches a formatter. It is a native <input type="date"> and stays YYYY-MM-DD whatever the tag. See Detail panel.

An empty string counts as no tag. A malformed tag such as "en_US" falls back to the built-in labels as well. Intl throws on it, and the chart catches that and warns once per tag per page load.

A well-formed but unknown tag such as "xx-YY" does not throw. Intl uses the runtime locale instead, so that typo produces no feedback.

Intl has no quarter field, so the chart assembles the quarter header itself. Q3 goes before a year string ending in a digit and after it otherwise, giving Q3 2025 and 2025년 Q3.

Replace a label with your own

formats is a record keyed by scale. Each scale takes any of the three label slots, and an omitted slot keeps the label from the layer below.

SlotFillsReceives
tickone label per cell in the bottom header rowthat cell's start date
headerone label per group in the top header rowthe group's start date
tooltipevery other date: the hover card, the bar names a screen reader reads, the read-only Start and End in the detail panel, and the drag readoutone date at a time

Each slot resolves on its own, from the first layer that fills it: formats[scale][slot], then locale, then the built-in English label. So formats.quarter.header beside locale="ko-KR" leaves tick and tooltip in Korean.

A fiscal year starting in April needs a quarter label Intl cannot produce.

fiscal.ts
import type { GanttFormatOverrides } from '@jaeungkim/gantt-chart';

// Module scope, so the object identity never changes between renders
export const formats: GanttFormatOverrides = {
  quarter: {
    header: (d) => {
      // The Dayjs is in UTC mode, so d.month() is 0 to 11 in UTC
      const fiscalMonth = (d.month() + 9) % 12;
      const fiscalYear = d.month() >= 3 ? d.year() : d.year() - 1;
      return `FY${fiscalYear} Q${Math.floor(fiscalMonth / 3) + 1}`;
    },
  },
};

Pass it as formats={formats}. The header slot receives the group's start date, so this override always sees January, April, July or October.

The Dayjs handed to a formatter is in UTC mode, so d.format('HH:mm'), d.year(), d.month() and d.day() return UTC values. A formatter that converts to local time prints a label that disagrees with its own cell.

locale never reaches dayjs either, so d.format('MMMM') prints September for every tag.

Declare formats at module scope or in useMemo. A new object literal on every render rebuilds every formatter behind the labels, and the chart does not warn.

Neighbouring groups whose labels read alike merge into one wider cell, so an override returning one string for two groups merges them.

During a move or resize, each end of the dragged span is written into the tick cell it lands in, in a compact form of the tooltip slot. An override is not compacted, and prints as it returns.

Start the week on another day

firstDayOfWeek is a number from 0 for Sunday to 6 for Saturday. Setting it groups the week scale's top header row by week from that day, instead of by month.

The chart checks whether the prop is present, so firstDayOfWeek={0} turns grouping on. On every other scale it does nothing. There is no validation: the value goes into a modulo, so 7 behaves as 0 and -1 as 6.

A grouped week header still reads its override from formats.week.header, because the record is keyed by the scale on screen. formats.day.header has no effect there.

Shading is separate. workingWeekdays and holidays decide which days are shaded. See The timeline.

What stays in English

The three props reach dates only. Every other string the chart renders is hardcoded English.

StringWhere it appears
Add taskthe button under the task list
Start, End, Duration, Progress, Depends onthe detail panel's field captions
12h, 3dthe duration shorthand in the hover card and the panel
Gantt chart, Task name, Timeline, Resize task list, Task details, Close task detailsthe accessible names of the chart, its two columns, the splitter, the panel and its close button
summary, to, % completethe connectives inside every bar's accessible name
<scale> scalethe announcement after a keyboard scale change
<name> deleted, <name> cannot be changed, <name> moved to 2 of 5 under <parent>the announcements after a keyboard edit

Only the dates inside those strings go through the localized tooltip formatter. The announcements themselves are in Keyboard and screen readers.

renderDetail replaces the panel's whole body, captions and close button included, so your app can translate that text. The panel's own accessible name stays English.

The chart renders no scale picker, so your app writes and translates its option text. See Imperative API.

Before you integrate

  • locale and formats change how a date is displayed, never how one is parsed. startDate and endDate are read as UTC whatever the tag, as Task data describes.
  • Positions and labels are UTC, and no prop renders them in local time. Shift your own date strings before passing them in.
  • Right-to-left layout is unsupported. Nothing sets or reads dir, so a right-to-left app mirrors the chart itself.
  • Number and plural formatting are absent. Intl.NumberFormat, Intl.RelativeTimeFormat and Intl.PluralRules are never constructed, so progress is announced as a raw number followed by % complete.

Props and defaults: GanttProps. The built-in format string for each scale is in Scale and theme types.