# Locale Provider

Gives the library the reader's locale, time zone and texts once per app, already translated.

```tsx
import * as React from "react"
import { LocaleProvider, type LocaleLabels } from "@zenoxai/ui/components/locale-provider"
import { NumberInput } from "@zenoxai/ui/components/number-input"
import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "@zenoxai/ui/components/pagination"
import { ToggleGroup, ToggleGroupItem } from "@zenoxai/ui/components/toggle-group"
import { EmptyValue } from "@zenoxai/ui/patterns/empty-value"
import { SeverityBadge } from "@zenoxai/ui/patterns/severity-badge"

function LocaleProviderDemo() {
  const [locale, setLocale] = React.useState("pt-BR")
  // In an app these come from its dictionary, once, kept stable.
  const labels = React.useMemo<LocaleLabels>(
    () =>
      locale === "pt-BR"
        ? {
            pagination: {
              previous: "Anterior",
              next: "Próxima",
              goToPrevious: "Ir para a página anterior",
              goToNext: "Ir para a próxima página",
              morePages: "Mais páginas",
            },
            numberInput: { decrease: "Diminuir", increase: "Aumentar" },
            severityBadge: {
              critical: "Crítica",
              high: "Alta",
              medium: "Média",
              low: "Baixa",
              unknown: "Desconhecida",
            },
            emptyValue: {
              unknown: "Sem valor",
              notApplicable: "Não se aplica",
              redacted: "Oculto",
            },
          }
        : {},
    [locale]
  )

  return (
    <div className="flex w-full max-w-md flex-col gap-6">
      <ToggleGroup
        variant="segmented"
        size="sm"
        value={[locale]}
        onValueChange={(value) => {
          if (value[0]) setLocale(value[0])
        }}
        aria-label="Locale"
      >
        <ToggleGroupItem value="en-US">en-US</ToggleGroupItem>
        <ToggleGroupItem value="pt-BR">pt-BR</ToggleGroupItem>
      </ToggleGroup>
      <LocaleProvider locale={locale} labels={labels}>
        <div className="flex flex-wrap items-center gap-4">
          <SeverityBadge level="high" />
          <EmptyValue reason="not-applicable" />
          <NumberInput defaultValue={12040} ariaLabel="Events per day" />
        </div>
        <Pagination>
          <PaginationContent>
            <PaginationItem>
              <PaginationPrevious href="#" />
            </PaginationItem>
            <PaginationItem>
              <PaginationLink href="#" isActive>
                1
              </PaginationLink>
            </PaginationItem>
            <PaginationItem>
              <PaginationEllipsis />
            </PaginationItem>
            <PaginationItem>
              <PaginationNext href="#" />
            </PaginationItem>
          </PaginationContent>
        </Pagination>
      </LocaleProvider>
    </div>
  )
}
```

## Usage

```tsx
import { LocaleProvider } from "@zenoxai/ui/components/locale-provider"
import { ptBR } from "react-day-picker/locale"
```

```tsx
<LocaleProvider
  locale="pt-BR"
  timeZone="America/Sao_Paulo"
  labels={labels}
  dateLocale={ptBR}
>
  <App />
  <Toaster />
  <ConfirmDialogHost />
</LocaleProvider>
```

Mount one at the root of the app, around the [Toaster](/components/sonner) and the
[confirmation host](/patterns/confirm-dialog) too. It sets:

- `locale`, a BCP 47 tag: the locale of every number the library formats, and the default of
  [Number Input](/components/number-input), [Progress](/components/progress),
  [Slider](/components/slider) and the chart tooltip.
- `timeZone`, an IANA name, for the app's own dates through `useLocale()`.
- `labels`, the library's own texts grouped by part, already translated. The app passes the
  results of its own `t()`; the library ships no translations.
- `dateLocale`, the locale of the [Calendar](/components/calendar), from `react-day-picker/locale`,
  which also translates the calendar's navigation.

Keep `labels` stable: build it once, in a module constant or a `useMemo` on the language. The
groups, their keys and how they fall back are on the [Localization](/foundations/localization)
page.

Without a provider every part shows its English default, as before.

## Examples

### Reading the locale in the app

`useLocale()` returns the provider's locale and time zone, and the functions of
[`lib/format`](/utilities/format) bound to them, for the app's own text.

```tsx
const { format } = useLocale()

format.number(1204)                                   // "1.204"
format.dateTime(alert.detectedAt, { seconds: true })  // "27/10/2026 15:05:09"
```

Outside a provider it returns en-US and the runtime's time zone, and never throws.

### A different text in one part of the page

A provider inside another merges its `labels` into the parent's, one group deep, and keeps the
parent's locale. An instance's own prop still wins over both.

```tsx
<LocaleProvider labels={{ dataTable: { empty: "Nenhuma regra" } }}>
  <DataTable table={rules} label="Regras" />
</LocaleProvider>
```

## Accessibility

- Set `lang` on `<html>` to the same language, so screen readers choose the right voice. The
  provider renders no element.
- The accessible names the library writes on its own (Close, Loading, Previous page) come from
  `labels`. In development, a part that falls back to English under another locale logs a
  warning that names the missing key.

## API reference

`import { ... } from "@zenoxai/ui/components/locale-provider"`

### LocaleContext

No props of its own.

### DEFAULT_LOCALE

No props of its own.

### createLocaleFormatters

No props of its own.

### useLocaleFormatters

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| dateLocale (required) | `DayPickerLocale \| undefined` |  |  |
| labels (required) | `LocaleLabels` |  |  |
| locale (required) | `string \| undefined` |  | The provider's locale, or undefined when only labels were given. |
| timeZone (required) | `string \| undefined` |  |  |

### LocaleProvider

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| dateLocale | `DayPickerLocale` |  | The date-fns locale of the Calendar and of FilterDate's calendar. Pass the one from `react-day-picker/locale`, which also translates the calendar's navigation ("Ir para o próximo mês"). |
| labels | `LocaleLabels` |  | The library's texts, already translated, by part. Keep the object stable (a module constant or a useMemo): a new object renders every part again. |
| locale | `string` |  | The BCP 47 tag of the reader's language, such as `pt-BR`: the locale of every number and date the library formats, and the default `locale` of NumberInput, Progress, Slider, FilterNumberRange and Chart's tooltip. A nested provider that only changes labels can leave it out. |
| timeZone | `string` |  | The IANA name of the reader's time zone, such as `America/Sao_Paulo`. The library formats no zoned date of its own: useLocale hands it to the app, and useFilterDateToday reads it. Defaults to the runtime's zone. |

### useLocale

No props of its own.

### useLocaleContext

No props of its own.

### useLabels

No props of its own.

