{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"lib-locale","type":"registry:lib","title":"locale helpers","description":"The language and reading direction the components speak: their built-in words (\"Search…\", \"Rows per page\"), the","dependencies":["radix-ui"],"registryDependencies":[],"files":[{"path":"registry/dashral/lib/locale.ts","type":"registry:file","target":"components/dashral/lib/locale.ts","content":"\"use client\"\n\n// The language and reading direction the components speak: their built-in words (\"Search…\", \"Rows per page\"), the\n// locale numbers and dates are written in, and right to left (Arabic). Without a LocaleProvider they're English,\n// left to right. Messages are plain strings with {placeholders}, or { one, other, … } plural forms, so a server\n// layout can pass them.\n\nimport * as React from \"react\"\nimport { Direction } from \"radix-ui\"\n\nexport type PluralMessage = Partial<Record<Intl.LDMLPluralRule, string>> & { other: string }\ntype Message = string | PluralMessage\n\nexport const defaultMessages = {\n  close: \"Close\",\n  cancel: \"Cancel\",\n  continue: \"Continue\",\n  navigation: \"Navigation\",\n  collapseSidebar: \"Collapse sidebar\",\n  expandSidebar: \"Expand sidebar\",\n  done: \"Done\",\n  dismiss: \"Dismiss\",\n  loading: \"Loading\",\n  remove: \"Remove {name}\",\n  selected: \"Selected\",\n  noResults: \"No results found.\",\n  noMatches: \"No matches\",\n  noData: \"No data\",\n  search: \"Search…\",\n  selectOption: \"Select an option\",\n  selectOptions: \"Select options\",\n  increase: \"Increase\",\n  decrease: \"Decrease\",\n  /** A stepper meeting its limit: \"Max 50\", \"Min 1\". */\n  atMax: \"Max {value}\",\n  atMin: \"Min {value}\",\n  /** A button that runs only when held (said to screen readers after its label). */\n  pressAndHold: \"(press and hold)\",\n  resize: \"Resize\",\n  copy: \"Copy\",\n  copied: \"Copied\",\n  copyFailed: \"Couldn't copy\",\n  showPassword: \"Show password\",\n  hidePassword: \"Hide password\",\n  // Dates\n  pickDate: \"Pick a date\",\n  pickDateRange: \"Pick a date range\",\n  pickTime: \"Pick a time\",\n  typeTime: \"Type a time\",\n  shortcuts: \"Shortcuts\",\n  today: \"Today\",\n  yesterday: \"Yesterday\",\n  last7Days: \"Last 7 days\",\n  last30Days: \"Last 30 days\",\n  thisMonth: \"This month\",\n  lastMonth: \"Last month\",\n  // Tags and files\n  addTag: \"Add a tag…\",\n  tags: \"Tags\",\n  tagLimit: { one: \"Up to {count} tag.\", other: \"Up to {count} tags.\" } as Message,\n  chosenFiles: \"Chosen files\",\n  dropFiles: \"Drop files here or {browse}\",\n  dropFile: \"Drop a file here or {browse}\",\n  browse: \"browse\",\n  fileType: \"{name}: this type of file isn't accepted.\",\n  fileSize: \"{name} is larger than {size}.\",\n  fileLimit: { one: \"{name}: up to {count} file.\", other: \"{name}: up to {count} files.\" } as Message,\n  upToSize: \"up to {size}\",\n  filesMax: { one: \"{count} file max\", other: \"{count} files max\" } as Message,\n  images: \"Images\",\n  videos: \"Videos\",\n  audio: \"Audio\",\n  textFiles: \"Text files\",\n  // Pages\n  pagination: \"Pagination\",\n  previous: \"Previous\",\n  next: \"Next\",\n  previousPage: \"Go to previous page\",\n  nextPage: \"Go to next page\",\n  firstPage: \"First page\",\n  lastPage: \"Last page\",\n  page: \"Page {page}\",\n  pageOf: \"Page {page} of {total}\",\n  morePages: \"More pages\",\n  // Carousel\n  carousel: \"Carousel\",\n  slideOf: \"{index} of {total}\",\n  previousSlide: \"Previous slide\",\n  nextSlide: \"Next slide\",\n  goToSlide: \"Go to slide {index}\",\n  pauseSlides: \"Pause\",\n  playSlides: \"Play\",\n  // Data table\n  searchTable: \"Search...\",\n  noRows: \"No results.\",\n  tryAnother: \"Try another search or clear the filters.\",\n  clearFilters: \"Clear filters\",\n  clearFilter: \"Clear filter\",\n  reset: \"Reset\",\n  export: \"Export\",\n  exportAll: \"Export all matching rows\",\n  exportSelected: \"Export {count} selected\",\n  columns: \"Columns\",\n  toggleColumns: \"Toggle columns\",\n  rowsPerPage: \"Rows per page\",\n  rows: { one: \"{count} row\", other: \"{count} rows\" } as Message,\n  rowsSelected: \"{selected} of {total} selected\",\n  selectAllRows: \"Select all rows on this page\",\n  selectRow: \"Select row\",\n  // Command palette\n  commandTitle: \"Command palette\",\n  commandDescription: \"Search for a command to run\",\n  commandPlaceholder: \"Type a command or search…\",\n  // Charts\n  less: \"Less\",\n  more: \"More\",\n  trend: \"Trend from {from} to {to}\",\n  heatmapSummary: \"{count} over {weeks} weeks to {date}\",\n  heatmapBusiest: \"; the busiest day was {date} with {value}.\",\n  donutChart: \"Donut chart\",\n  treemap: \"Treemap\",\n  treemapLevels: \"Treemap levels\",\n  all: \"All\",\n  zoomIn: \"{name}. Zoom in\",\n  candlestickChart: \"Candlestick chart\",\n  areaChart: \"Area chart\",\n  lineChart: \"Line chart\",\n  barChart: \"Bar chart\",\n  candle: \"{date}: open {open}, high {high}, low {low}, close {close}\",\n  candleOpen: \"O\",\n  candleHigh: \"H\",\n  candleLow: \"L\",\n  candleClose: \"C\",\n  volume: \"Vol\",\n  // AI\n  message: \"Message\",\n  askAnything: \"Ask anything…\",\n  sendMessage: \"Send message\",\n  stopGenerating: \"Stop generating\",\n  attachFiles: \"Attach files\",\n  attachments: \"Attachments\",\n  dropToAttach: \"Drop files to attach them\",\n  charactersLeft: { one: \"{count} character left\", other: \"{count} characters left\" } as Message,\n  charactersOver: { one: \"{count} character over the limit\", other: \"{count} characters over the limit\" } as Message,\n  you: \"You\",\n  assistant: \"Assistant\",\n  regenerate: \"Regenerate\",\n  goodResponse: \"Good response\",\n  badResponse: \"Bad response\",\n  messageFailed: \"Couldn't finish this answer.\",\n  tryAgain: \"Try again\",\n  conversation: \"Conversation\",\n  jumpToLatest: \"Jump to latest\",\n  thinking: \"Thinking…\",\n  thoughtFor: { one: \"Thought for {count} second\", other: \"Thought for {count} seconds\" } as Message,\n  thoughtBriefly: \"Thought for a moment\",\n  toolActivity: \"Tool activity\",\n  toolPending: \"Waiting\",\n  toolRunning: \"Running\",\n  toolDone: \"Done\",\n  toolFailed: \"Failed\",\n  toolInput: \"Input\",\n  toolOutput: \"Output\",\n  toolStatus: \"{name}: {status}\",\n  needsApproval: \"Needs your approval\",\n  approve: \"Approve\",\n  allowOnce: \"Allow once\",\n  allowAlways: \"Always allow\",\n  deny: \"Deny\",\n  approved: \"Approved\",\n  denied: \"Denied\",\n  alwaysAllowed: \"Always allowed\",\n  suggestions: \"Suggestions\",\n  // Rating\n  rating: \"Rating\",\n  ratingStars: { one: \"{count} star\", other: \"{count} stars\" } as Message,\n  ratingOf: \"{value} out of {max}\",\n  // Colors\n  pickColor: \"Pick a color\",\n  colorArea: \"Saturation and brightness\",\n  colorAreaValue: \"Saturation {saturation}%, brightness {brightness}%\",\n  hue: \"Hue\",\n  swatches: \"Swatches\",\n  pickFromScreen: \"Pick a color from the screen\",\n  // Phone numbers\n  country: \"Country\",\n  searchCountries: \"Search countries…\",\n  // Lightbox\n  imageViewer: \"Image viewer\",\n  viewImage: \"View {name}\",\n  imageOf: \"{index} of {total}\",\n  previousImage: \"Previous image\",\n  nextImage: \"Next image\",\n  // Selection and forms\n  selectAll: \"Select all\",\n  selectedCount: { one: \"{count} selected\", other: \"{count} selected\" } as Message,\n  selectionActions: \"Actions for the selection\",\n  clearSelection: \"Clear selection\",\n  time: \"Time\",\n  // Passwords\n  passwordStrength: \"Password strength\",\n  passwordWeak: \"Weak\",\n  passwordFair: \"Fair\",\n  passwordGood: \"Good\",\n  passwordStrong: \"Strong\",\n  ruleLength: { one: \"At least {count} character\", other: \"At least {count} characters\" } as Message,\n  ruleCase: \"Upper and lower case letters\",\n  ruleNumber: \"A number\",\n  ruleSymbol: \"A symbol\",\n  ruleMet: \"(met)\",\n  ruleNotMet: \"(not met yet)\",\n  // Editing in place\n  edit: \"Edit\",\n  cantBeEmpty: \"This can't be empty.\",\n  saveFailed: \"Couldn't save. Try again.\",\n  // Time zones\n  pickTimeZone: \"Select a time zone\",\n  searchTimeZones: \"Search time zones…\",\n  // JSON\n  jsonKeys: { one: \"{count} key\", other: \"{count} keys\" } as Message,\n  jsonItems: { one: \"{count} item\", other: \"{count} items\" } as Message,\n  showMore: \"Show {count} more\",\n}\n\nexport type LocaleMessages = typeof defaultMessages\nexport type MessageKey = keyof LocaleMessages\n\ntype LocaleContextValue = {\n  /** BCP 47, e.g. \"en-US\", \"id-ID\", \"ar\". Numbers and dates are written in it. */\n  locale: string\n  dir: \"ltr\" | \"rtl\"\n  messages: LocaleMessages\n  /** For Calendar and the date pickers: a react-day-picker locale (month and day names, first day of the week). */\n  dateLocale?: unknown\n}\n\nconst LocaleContext = React.createContext<LocaleContextValue>({ locale: \"en-US\", dir: \"ltr\", messages: defaultMessages })\n\n/**\n * Sets the language and direction for the components inside, and Radix's direction (menus, sliders and tabs follow\n * it with the keyboard). Pass only the messages you translate; the rest stay English.\n */\nexport function LocaleProvider({\n  locale,\n  dir = \"ltr\",\n  messages,\n  dateLocale,\n  children,\n}: {\n  locale: string\n  dir?: \"ltr\" | \"rtl\"\n  messages?: Partial<LocaleMessages>\n  dateLocale?: unknown\n  children: React.ReactNode\n}) {\n  const value = React.useMemo(\n    () => ({ locale, dir, messages: { ...defaultMessages, ...messages }, dateLocale }),\n    [locale, dir, messages, dateLocale]\n  )\n  return React.createElement(LocaleContext.Provider, { value }, React.createElement(Direction.Provider, { dir }, children))\n}\n\n/** Fills {placeholders} (numbers written in the locale); a plural message picks its form by `count` with the locale's rules (Arabic has six). */\nexport function formatMessage(message: Message, values: Record<string, React.ReactNode> = {}, locale = \"en-US\"): string {\n  const template =\n    typeof message === \"string\"\n      ? message\n      : (message[new Intl.PluralRules(locale).select(Number(values.count ?? 0))] ?? message.other)\n  // Numbers are written in the locale (1,175 · 1.175 · ١٬١٧٥ by its own rules).\n  const number = new Intl.NumberFormat(locale)\n  return template.replace(/\\{(\\w+)\\}/g, (match, key: string) => {\n    if (!(key in values)) return match\n    const value = values[key]\n    return typeof value === \"number\" ? number.format(value) : String(value)\n  })\n}\n\n/** The locale, direction and a translator for the components' words: t(\"pageOf\", { page: 2, total: 7 }). */\nexport function useLocale() {\n  const context = React.useContext(LocaleContext)\n  const t = React.useCallback(\n    (key: MessageKey, values?: Record<string, React.ReactNode>) => formatMessage(context.messages[key], values, context.locale),\n    [context]\n  )\n  return { ...context, t }\n}\n"}]}