{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "locale",
  "title": "goodui locale",
  "description": "Set the language once and every goodui component speaks it: <GooduiLocaleProvider lang labels>, useGooduiLocale() with Intl number, date, relative and currency formatters, useLabels() and RTL direction. English and en-US without it.",
  "files": [
    {
      "path": "lib/locale.ts",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\n/*\n * goodui locale: set the language once and every goodui component speaks it.\n *\n *   <GooduiLocaleProvider lang=\"tr\" labels={tr}>…</GooduiLocaleProvider>\n *       lang    BCP 47 tag used for every Intl format inside the kit (\"tr\", \"de-AT\", \"pt-BR\"…)\n *       labels  a language pack (lib/goodui-locales/<lang>.ts) or your own { [componentId]: { [key]: … } }\n *       dir     \"ltr\" | \"rtl\"; derived from lang when omitted (ar, he, fa, ur are rtl)\n *\n *   useGooduiLocale()                      { lang, dir, labels, provided, formatNumber, formatDate, formatRelative, formatCurrency }\n *   useLabels(id, DEFAULT_LABELS, labels)  what components call: defaults ← pack[id] ← the instance's `labels` prop\n *\n * Without a provider everything is English and \"en-US\" formats, exactly as before, and the context read costs\n * nothing measurable. The provider never touches <html dir> or <html lang>: set them yourself\n * (`<html lang=\"ar\" dir=\"rtl\">`), because components read the writing direction from the DOM. SSR-safe: no effects.\n */\n\nexport type GooduiDir = \"ltr\" | \"rtl\"\n\n/** One label: a string, or a function of the component's values that returns text (or a node, e.g. pagination.pageOf). */\nexport type LabelValue = string | ((...args: never[]) => React.ReactNode)\n\n/** A language pack: component id → its labels. Partial on both levels, so a pack can cover only what an app uses. */\nexport type LabelPack = { readonly [componentId: string]: { readonly [key: string]: LabelValue } | undefined }\n\n/** Languages goodui ships a pack for, with their native names, for a language switcher. */\nexport const GOODUI_LANGUAGES: readonly { code: string; name: string; dir: GooduiDir }[] = [\n  { code: \"en\", name: \"English\", dir: \"ltr\" },\n  { code: \"tr\", name: \"Türkçe\", dir: \"ltr\" },\n  { code: \"de\", name: \"Deutsch\", dir: \"ltr\" },\n  { code: \"fr\", name: \"Français\", dir: \"ltr\" },\n  { code: \"es\", name: \"Español\", dir: \"ltr\" },\n  { code: \"it\", name: \"Italiano\", dir: \"ltr\" },\n  { code: \"pt-BR\", name: \"Português (Brasil)\", dir: \"ltr\" },\n  { code: \"nl\", name: \"Nederlands\", dir: \"ltr\" },\n  { code: \"pl\", name: \"Polski\", dir: \"ltr\" },\n  { code: \"ru\", name: \"Русский\", dir: \"ltr\" },\n  { code: \"hi\", name: \"हिन्दी\", dir: \"ltr\" },\n  { code: \"id\", name: \"Bahasa Indonesia\", dir: \"ltr\" },\n  { code: \"vi\", name: \"Tiếng Việt\", dir: \"ltr\" },\n  { code: \"th\", name: \"ไทย\", dir: \"ltr\" },\n  { code: \"ja\", name: \"日本語\", dir: \"ltr\" },\n  { code: \"ko\", name: \"한국어\", dir: \"ltr\" },\n  { code: \"zh-CN\", name: \"中文（简体）\", dir: \"ltr\" },\n  { code: \"ar\", name: \"العربية\", dir: \"rtl\" },\n  { code: \"he\", name: \"עברית\", dir: \"rtl\" },\n]\n\nconst RTL = new Set([\"ar\", \"he\", \"fa\", \"ur\", \"iw\", \"ps\", \"sd\", \"ug\", \"yi\", \"dv\", \"ckb\"])\n\n/** Writing direction of a language tag: rtl for Arabic, Hebrew, Persian, Urdu (and the other RTL scripts), else ltr. */\nexport function dirOf(lang: string): GooduiDir {\n  const base = lang.split(/[-_]/)[0]?.toLowerCase() ?? \"\"\n  return RTL.has(base) ? \"rtl\" : \"ltr\"\n}\n\ntype Ctx = { lang: string; dir: GooduiDir; labels: LabelPack; provided: boolean }\n\nconst DEFAULT_CTX: Ctx = { lang: \"en-US\", dir: \"ltr\", labels: {}, provided: false }\nconst LocaleContext = React.createContext<Ctx>(DEFAULT_CTX)\n\nexport type GooduiLocaleProviderProps = {\n  /** BCP 47 language tag: \"tr\", \"de\", \"pt-BR\", \"ar\"… Drives every Intl format in the kit. */\n  lang: string\n  /** A language pack from lib/goodui-locales, or your own labels keyed by component id. */\n  labels?: LabelPack\n  /** Writing direction. Derived from `lang` when omitted. Set <html dir> yourself as well; the provider only tells components. */\n  dir?: GooduiDir\n  children?: React.ReactNode\n}\n\nexport function GooduiLocaleProvider({ lang, labels, dir, children }: GooduiLocaleProviderProps) {\n  const value = React.useMemo<Ctx>(() => ({ lang, dir: dir ?? dirOf(lang), labels: labels ?? {}, provided: true }), [lang, dir, labels])\n  return React.createElement(LocaleContext.Provider, { value }, children)\n}\n\nexport type RelativeUnit = Intl.RelativeTimeFormatUnit\n\nexport type GooduiLocale = {\n  /** The provider's language, or \"en-US\" without one. */\n  lang: string\n  dir: GooduiDir\n  /** The provider's pack ({} without one). */\n  labels: LabelPack\n  /** true when a GooduiLocaleProvider is mounted above. */\n  provided: boolean\n  formatNumber: (value: number, options?: Intl.NumberFormatOptions) => string\n  /** Date, timestamp or ISO string. Default style: medium date (\"3 Nov 2026\"). */\n  formatDate: (value: Date | number | string, options?: Intl.DateTimeFormatOptions) => string\n  /** \"in 3 days\", \"2 hours ago\", \"yesterday\" (numeric \"auto\" by default). */\n  formatRelative: (value: number, unit: RelativeUnit, options?: Intl.RelativeTimeFormatOptions) => string\n  /** ISO 4217 code; defaults to USD. */\n  formatCurrency: (value: number, currency?: string, options?: Intl.NumberFormatOptions) => string\n}\n\n// Intl constructors are slow; keep one instance per lang + options.\nconst cache = new Map<string, unknown>()\nfunction cached<T>(kind: string, lang: string, options: object | undefined, make: () => T): T {\n  const key = `${kind}|${lang}|${options ? JSON.stringify(options) : \"\"}`\n  let f = cache.get(key) as T | undefined\n  if (!f) {\n    if (cache.size > 200) cache.clear()\n    f = make()\n    cache.set(key, f)\n  }\n  return f\n}\n\nfunction safe<T>(make: (lang: string) => T, lang: string): T {\n  try {\n    return make(lang)\n  } catch {\n    return make(\"en-US\")\n  }\n}\n\n/** Formatters for a language; what useGooduiLocale() returns, for use outside React (loaders, server code). */\nexport function createFormatters(lang: string): Pick<GooduiLocale, \"formatNumber\" | \"formatDate\" | \"formatRelative\" | \"formatCurrency\"> {\n  return {\n    formatNumber: (value, options) => cached(\"n\", lang, options, () => safe((l) => new Intl.NumberFormat(l, options), lang)).format(value),\n    formatDate: (value, options = { dateStyle: \"medium\" }) => {\n      const d = value instanceof Date ? value : new Date(value)\n      return Number.isNaN(d.getTime()) ? \"\" : cached(\"d\", lang, options, () => safe((l) => new Intl.DateTimeFormat(l, options), lang)).format(d)\n    },\n    formatRelative: (value, unit, options = { numeric: \"auto\" }) => cached(\"r\", lang, options, () => safe((l) => new Intl.RelativeTimeFormat(l, options), lang)).format(value, unit),\n    formatCurrency: (value, currency = \"USD\", options) => {\n      const o = { style: \"currency\", currency, ...options } as Intl.NumberFormatOptions\n      return cached(\"c\", lang, o, () => safe((l) => new Intl.NumberFormat(l, o), lang)).format(value)\n    },\n  }\n}\n\n/** The current language, direction, pack and Intl formatters. English + \"en-US\" without a provider. */\nexport function useGooduiLocale(): GooduiLocale {\n  const ctx = React.useContext(LocaleContext)\n  return React.useMemo(() => ({ ...ctx, ...createFormatters(ctx.lang) }), [ctx])\n}\n\n/**\n * Resolves a component's labels: its English defaults, then the provider pack's entry for `componentId`, then the\n * instance's own `labels` prop. Undefined overrides are skipped so `labels={{ close: cond ? \"X\" : undefined }}` is safe.\n */\nexport function useLabels<T extends object>(componentId: string, defaults: T, overrides?: Partial<T>): T {\n  const pack = React.useContext(LocaleContext).labels[componentId]\n  if (!pack && !overrides) return defaults\n  const out: Record<string, unknown> = { ...defaults, ...pack }\n  if (overrides) for (const k in overrides) if (overrides[k] !== undefined) out[k] = overrides[k]\n  return out as T\n}\n\n/**\n * Plural helper for packs: `plural(lang, n, { one: \"1 satır\", other: `${n} satır` })`.\n * Categories follow Intl.PluralRules (zero, one, two, few, many, other); `other` is the fallback.\n */\nexport function plural(lang: string, n: number, forms: Partial<Record<Intl.LDMLPluralRule, string>> & { other: string }): string {\n  const rule = cached(\"p\", lang, undefined, () => safe((l) => new Intl.PluralRules(l), lang))\n  return forms[rule.select(n)] ?? forms.other\n}\n",
      "type": "registry:lib",
      "target": "lib/goodui-locale.ts"
    }
  ],
  "type": "registry:lib"
}