{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "i18n",
  "title": "i18n Utilities",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "Translate every built-in string and set the locale in one place: I18nProvider takes a messages object, useMessages looks text up, useLocale feeds Intl formatting.",
  "files": [
    {
      "path": "registry/ballmac/lib/i18n.tsx",
      "content": "// Ballmac UI: i18n utilities. https://ui.ballmac.com/docs/i18n\n\"use client\"\n\nimport * as React from \"react\"\n\n/** A flat dictionary: message key to translated text. Keys and English defaults are listed at /i18n/en.json. */\nexport type Messages = Record<string, string>\n\ntype Values = Record<string, string | number>\ntype I18nContextValue = { messages: Messages; locale: string | undefined }\n\nconst I18nContext = React.createContext<I18nContextValue>({ messages: {}, locale: undefined })\n\nconst subscribe = () => () => {}\n\n/**\n * Translates every built-in string (aria-labels, placeholders, hidden text, default button labels) and sets the locale that\n * dates, numbers and sorting use. Put it once near the root. Anything you leave out stays in English.\n *\n *   <I18nProvider locale=\"ar\" messages={{ \"pagination.nextPage\": \"الصفحة التالية\" }}>\n */\nexport function I18nProvider({ messages = {}, locale, children }: { messages?: Messages; locale?: string; children: React.ReactNode }) {\n  const value = React.useMemo(() => ({ messages, locale }), [messages, locale])\n  return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>\n}\n\n/**\n * The locale for Intl formatting: the provider's, else `<html lang>`, else `fallback`.\n * Server render and the first client render use the provider or the fallback, so markup matches.\n */\nexport function useLocale(fallback = \"en-US\"): string {\n  const { locale } = React.useContext(I18nContext)\n  const documentLang = React.useSyncExternalStore(subscribe, () => document.documentElement.lang, () => \"\")\n  return locale ?? (documentLang || fallback)\n}\n\nfunction fill(template: string, values?: Values) {\n  return values ? template.replace(/\\{(\\w+)\\}/g, (all, name: string) => (name in values ? String(values[name]) : all)) : template\n}\n\n/** English text, or its plural forms keyed by CLDR category (`other` is required). */\nexport type Fallback = string | ({ [K in Intl.LDMLPluralRule]?: string } & { other: string })\n\n/** A string declared at module level (a status label in a lookup table); render it with `msg.of(message)`. */\nexport type Message = { key: string; text: string }\nexport const defineMessage = (key: string, text: string): Message => ({ key, text })\n\nexport type Msg = {\n  (key: string, fallback: Fallback, values?: Values): string\n  /** Translate a message declared with defineMessage. */\n  of: (message: Message, values?: Values) => string\n  /** Like `msg`, but `{name}` placeholders are filled with React nodes: msg.rich(\"k\", \"Press {key} to open\", { key: <Kbd>K</Kbd> }). */\n  rich: (key: string, fallback: string, nodes: Record<string, React.ReactNode>) => React.ReactNode\n}\n\n/**\n * Returns `msg(key, english, values?)`. The text comes from the provider's messages, else the English default.\n * `{name}` placeholders are filled from `values`. With a numeric `count`, a plural form is used: the dictionary's\n * `key_one`, `key_few`, `key_many` or `key_other` (the CLDR categories of the locale), else the English forms you pass as\n * `{ one: \"{count} item\", other: \"{count} items\" }`.\n */\nexport function useMessages(): Msg {\n  const { messages } = React.useContext(I18nContext)\n  const locale = useLocale()\n  return React.useMemo(() => {\n    const msg = ((key: string, fallback: Fallback, values?: Values) => {\n      let template: string | undefined\n      if (values && typeof values.count === \"number\") {\n        try {\n          const category = new Intl.PluralRules(locale).select(values.count)\n          template = messages[`${key}_${category}`] ?? (typeof fallback === \"object\" && !(key in messages) ? (fallback[category] ?? fallback.other) : undefined)\n        } catch {\n          // An invalid locale tag falls back to the plain key.\n        }\n      }\n      template ??= messages[key] ?? (typeof fallback === \"string\" ? fallback : fallback.other)\n      return fill(template, values)\n    }) as Msg\n    msg.of = (message, values) => msg(message.key, message.text, values)\n    msg.rich = (key, fallback, nodes) => {\n      const template = messages[key] ?? fallback\n      return React.createElement(\n        React.Fragment,\n        null,\n        ...template.split(/(\\{\\w+\\})/).map((part, i) => {\n          const name = /^\\{(\\w+)\\}$/.exec(part)?.[1]\n          return name && name in nodes ? React.createElement(React.Fragment, { key: i }, nodes[name]) : part\n        })\n      )\n    }\n    return msg\n  }, [messages, locale])\n}\n",
      "type": "registry:lib",
      "target": "@lib/ballmac/i18n.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "i18n",
      "translation",
      "locale",
      "internationalization",
      "arabic",
      "labels",
      "plural"
    ],
    "version": "1.0.0",
    "updated": "2026-10-02",
    "ai": {
      "summary": "Wrap the app in <I18nProvider locale messages>. Every Ballmac component looks its built-in text up by key with the English text as the default, so nothing breaks when a key is missing. The key list is at /i18n/en.json.",
      "whenToUse": [
        "Localising an app that uses Ballmac components",
        "Setting one locale for dates, numbers and sorting"
      ],
      "whenNotToUse": [
        "Your own app copy: use your i18n library (next-intl, i18next, Lingui) and pass its output in through props or messages"
      ],
      "composesWith": [],
      "a11y": [],
      "customization": [
        "messages: a flat key to text object, with {name} placeholders and _one/_few/_many/_other plural suffixes",
        "locale: a BCP 47 tag such as ar, he, fa-IR, de-DE"
      ]
    },
    "examples": [],
    "url": "https://ui.ballmac.com/components/i18n"
  },
  "categories": [
    "foundation"
  ],
  "type": "registry:lib"
}