---
title: "Localization"
description: "Choose a buyer locale, override product copy, and understand language fallback and money formatting."
---

Buyer surfaces ship in 37 languages. Pass a locale when your application
already knows the buyer's language, or leave it out and let the SDK resolve it
from the buyer's browser.

```js title="buyer/localization.js"
const picker = new seatlayer.SeatPicker({
  container: "#picker",
  event: "ev_9f3a",
  publicKey: "pk_test_…",
  locale: "de-DE",
  messages: {
    "map.fromPrice": "ab {price}",
    "picker.holdSeatsAndCheckout": "Weiter zur Kasse",
  },
});

await picker.render();
```

For a public Platform event, use a publishable key that matches the event mode
(`pk_test_…` for test or `pk_live_…` for live) and register the page's exact
Embed origin. The SDK obtains Public-only access directly and keeps the grant in
memory, so your server does not mint a buyer token when the chart loads. For a
login, presale, partner, or channel audience, replace `publicKey` with an async
`buyerAccessTokenProvider` backed by your authenticated server; an explicit
provider or token takes precedence.

## Locale resolution

SeatLayer resolves language in this order:

1. explicit `locale`;
2. `navigator.languages`, in the buyer's own priority order;
3. English.

Tags match most-specific-first. `es-MX` uses Spanish and `fr-CA` uses French,
and script subtags survive: `zh-Hant-TW` gets Traditional Chinese, not
Simplified. Unsupported languages fall back to English.

English is compiled into the SDK. The other 36 load on demand — one small file,
only for the language actually used — and `render()` resolves after it is
ready. Adding languages does not grow the bundle every buyer downloads.

<Aside type="caution" title="Language is fixed at render">
  `locale` is read once, when `render()` runs. There is no way to change the
  language of a live chart today; you would have to `destroy()` and re-create
  it, which loses the buyer's selection.
</Aside>

## Override individual messages

`messages` is a sparse map layered over the active locale. Use it for
white-label product language, not for rendering untrusted user input.

```js title="buyer/copy.js"
const messages = {
  "picker.continueToCheckout": "Confirm tickets",
  "picker.holdExpired":
    "Your reservation expired. Choose your seats again.",
  "picker.seatCount.one": "{count} ticket",
  "picker.seatCount.other": "{count} tickets",
};
```

Placeholders such as `{count}`, `{price}`, and `{label}` are interpolated by the
SDK. If a key is absent, SeatLayer falls back to the selected locale, then
English, then the key itself.

Plural keys carry one entry per CLDR category **for the language you are
overriding**, which is not always `.one` and `.other`. English and German need
two; Polish needs `.one`, `.few`, `.many`, `.other`; Arabic and Welsh need all
six. `Intl.PluralRules(locale).resolvedOptions().pluralCategories` gives the
exact set. A category you leave out falls back to `.other`, which parses fine
and reads wrong.

<Aside type="caution" title="Keys are matched exactly">
  An override for a key the SDK never asks for does nothing, silently. Check a
  key against the shipped English dictionary before relying on it.
</Aside>

<Aside type="tip" title="Keep overrides small">
  Override the language your product owns and rely on the shipped dictionary
  for standard seating behavior. A small override set is easier to review when
  the SDK adds a new state.
</Aside>

## Common keys

| Key | English purpose |
|---|---|
| `map.aria` | Seating-map screen-reader instructions |
| `map.seatsLeft` | Remaining inventory label |
| `map.fromPrice` | Starting-price label |
| `picker.holdSeatsAndCheckout` | Main hold and checkout action |
| `picker.continueToCheckout` | Direct-booking action |
| `picker.addMinutes` | One-tap hold extension |
| `picker.holdExpired` | Expired-hold recovery |
| `picker.seatTaken` | Concurrent-selection conflict |
| `picker.ticketPrices` | Ticket-type filter heading |
| `picker.hideLimitedView` | Limited-view accessibility filter |
| `picker.toggleColorblindColors` | Colorblind-safe palette control |
| `picker.yourSeats` | Selection summary |
| `picker.total` | Buyer total |
| `picker.seatCount.one` / `.other` | Quantity-aware seat count |

Use the exported SDK types and current dictionary as the exact reference when
maintaining a comprehensive custom translation.

## Locale and currency

Locale changes number formatting, separator placement, dates, and plural rules.
It does not convert prices or choose the order currency. Currency resolves
independently from event/org data, the SDK fallback, and USD.

```js title="buyer/french-euros.js"
new seatlayer.SeatingChart({
  container: "#chart",
  event: "ev_9f3a",
  publicKey: "pk_test_…",
  locale: "fr",
  currency: "EUR", // fallback only when the event carries no currency
});
```

## Localization review

- [ ] Every explicit locale has a supported fallback.
- [ ] Overrides preserve required placeholders.
- [ ] Singular and plural actions read naturally.
- [ ] Every plural category the language requires is present, not just two.
- [ ] Long German and Finnish labels fit on a narrow screen.
- [ ] Arabic, Hebrew and Persian render right-to-left inside your page.
- [ ] Screen-reader instructions match the actual controls.
- [ ] Price formatting is tested separately from the price source.

Continue to [currency and pricing](/customization/currency-and-pricing) or the
[SeatingChart reference](/buyer-sdk/seating-chart).