Localization

Every built-in string in the kit (468 labels across 77 label sets: aria-labels, screen-reader announcements, the text of buttons you did not write, placeholders) is translated by one provider. Mount it once with a language pack and every goodui component speaks that language; number, date and currency formats follow through Intl, and Arabic and Hebrew get their direction. Apps that never set it keep English and en-US formats, with nothing extra loaded.

Install

npx shadcn@latest add https://goodui.dev/r/locale.json https://goodui.dev/r/locale-tr.json

locale lands in lib/goodui-locale.ts (every component with labels already depends on it, so you usually have it). A pack lands in lib/goodui-locales/<lang>.ts and depends on nothing else; install only the languages you ship.

Set the language

app/layout.tsx
import { GooduiLocaleProvider } from "@/lib/goodui-locale"
import tr from "@/lib/goodui-locales/tr"

export default function RootLayout({ children }) {
  return (
    <html lang="tr">
      <body>
        <GooduiLocaleProvider lang="tr" labels={tr}>{children}</GooduiLocaleProvider>
      </body>
    </html>
  )
}

lang is a BCP 47 tag and drives every Intl format in the kit ("de-AT", "pt-BR", "en-GB" are fine). labels is the pack. For a language switcher, keep the tag in state and pick the pack from a map of lazy imports; nested providers work, so one screen can run in another language.

Packs

18 languages, one file each, every label of every component. Native names as a switcher would show them:

  • locale-tr: Türkçe
  • locale-de: Deutsch
  • locale-fr: Français
  • locale-es: Español
  • locale-it: Italiano
  • locale-pt-BR: Português (Brasil)
  • locale-nl: Nederlands
  • locale-pl: Polski
  • locale-ru: Русский
  • locale-hi: हिन्दी
  • locale-id: Bahasa Indonesia
  • locale-vi: Tiếng Việt
  • locale-th: ไทย
  • locale-ja: 日本語
  • locale-ko: 한국어
  • locale-zh-CN: 中文(简体)
  • locale-ar: العربية (RTL)
  • locale-he: עברית (RTL)

locale-en exists too: it is generated from the components and is what every other pack is checked against. Import it when you want to change English labels app-wide.

RTL

Components read the writing direction from the DOM (logical properties, :dir(rtl), flipped icons), so set it on the document yourself: <html lang="ar" dir="rtl">. The provider never touches <html>; it only derives dir from lang (ar, he, fa, ur are RTL) for components that ask, and you can pass dir to override it. dirOf("ar") is exported for your own layout code.

Intl formats

Components that format values (number-flow, counter-up, stat-card, meter, sparkline-draw, data-grid, date-picker, number-input) take a locale prop. Unset, it is now the provider language instead of "en-US"; an explicit prop still wins. The same formatters are yours through the hook:

import { useGooduiLocale } from "@/lib/goodui-locale"

const { lang, dir, formatNumber, formatDate, formatRelative, formatCurrency } = useGooduiLocale()

formatNumber(12345.6)                       // "12.345,6" in de
formatDate(booking.start)                   // "3 Kas 2026" in tr; pass Intl.DateTimeFormat options for other styles
formatRelative(-2, "hour")                  // "vor 2 Stunden"
formatCurrency(49, "EUR")                   // "49,00 €"

Instances are cached per language and options, so calling them in render is fine. Outside React, createFormatters(lang) returns the same four functions.

Override one label

The labels prop on any component still wins over the pack, so a single instance can say something else. For an app-wide change, spread the pack:

<Dialog labels={{ confirm: "Rezervasyonu iptal et" }} … />        {/* this dialog only */}

const labels = { ...tr, alert: { ...tr.alert, dismiss: "Kapat" } }   // every alert
<GooduiLocaleProvider lang="tr" labels={labels}>

Labels with values are functions, so plurals are real: results: (n) => plural("pl", n, { one: "1 wynik", few: `${n} wyniki`, many: `${n} wyników`, other: `${n} wyniku` }). plural() ships in lib/goodui-locale and wraps Intl.PluralRules.

Add a language

Copy lib/goodui-locales/en.ts to a new file and translate the values; keep the keys and the function signatures. The manifest, lib/locales/labels-manifest.json in the repo, lists every key with its kind, parameters and where it is used (aria-label, screen reader, visible text). Our own build runs node scripts/extract-labels.ts, which regenerates lib/goodui-locales/en.ts from the components and fails when any pack is missing a component, a key or a parameter, so the 18 packs can never drift behind the components. Pull requests with new languages are welcome.

Try it

Every Core component's Playground has a language select: the preview re-renders in that language (RTL for Arabic and Hebrew) and the printed snippet includes the provider. Any preview page takes ?lang=tr (and &dir=rtl to force a direction), e.g. /preview/c/pagination?lang=ar.

goodui Native

The same packs and manifest are planned for the React Native kit (a LocaleProvider in foundation, the same component ids); until then, Native components take their strings through props and labels objects per screen.