# Locale

"rtl" for Arabic, Hebrew, Persian, Urdu and the like; "ltr" otherwise.

- Registry name: `locale` (`@candor/locale`)
- Tier: Free (MIT). No licence key needed.
- Docs: https://candor.themethodic.com/docs/locale

## Install

```bash
npx candor-ui add locale
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/locale`.
npm packages: radix-ui@^1.8.1.

## Example

```tsx
"use client"

import * as React from "react"
import { LocaleProvider } from "@/components/locale"
import { RelativeTime } from "@/components/relative-time"
import { Composer } from "@/components/composer"
import { Message } from "@/components/message"
import { Thread } from "@/components/thread"
import type { Message as MessageData } from "@/lib/chat"

const agent = { id: "a1", name: "وكيل الدعم", role: "assistant" }
const at = new Date(Date.now() - 4 * 60_000).toISOString()

const messages: MessageData[] = [
  { id: "u1", author: { role: "user" }, createdAt: at, status: "complete", blocks: [{ kind: "plain", text: "أين طلبي رقم 1042؟" }] },
  {
    id: "a1",
    author: { role: "agent", agent },
    createdAt: at,
    status: "complete",
    blocks: [{ kind: "paragraph", nodes: [{ kind: "text", text: "شُحن طلبك أمس، ويصل خلال يومين." }] }],
  },
]

/** Arabic, right to left: the provider sets the words, the locale and the direction; `dir` on the wrapper flips the layout. */
const arabic = {
  thread: { messages: "الرسائل", jumpToLatest: "الانتقال إلى الأحدث" },
  message: { you: "أنت" },
  composer: { label: "رسالة", placeholder: "اكتب سؤالك", send: "إرسال", hintSend: "إرسال", hintNewLine: "سطر جديد", hintMention: "إشارة" },
  relativeTime: { justNow: "الآن" },
}

export default function Example() {
  const [value, setValue] = React.useState("")
  return (
    <div className="flex flex-col gap-4">
      <LocaleProvider locale="ar" strings={arabic}>
        <div dir="rtl" lang="ar" className="h-80 overflow-hidden rounded-lg border">
          <Thread className="h-full" footer={<Composer value={value} onChange={setValue} onSubmit={() => setValue("")} />}>
            {messages.map((m) => (
              <Message key={m.id} message={m} />
            ))}
          </Thread>
        </div>
      </LocaleProvider>
      <LocaleProvider locale="fr-FR" timeZone="Europe/Paris">
        <p lang="fr" className="text-sm text-muted-foreground">
          Envoyé <RelativeTime date={at} /> · <RelativeTime date={at} format="datetime" />
        </p>
      </LocaleProvider>
    </div>
  )
}
```

## <LocaleProvider>

Props:

- `locale?: string | undefined`: A BCP 47 tag such as "en-GB", "fr" or "ar-EG", for dates, numbers and plural forms. Left out, the browser's own is used once the page has hydrated.
- `dir?: TextDirection | undefined`: Text direction. Left out, it follows the locale: "rtl" for Arabic, Hebrew, Persian, Urdu and the like. Set `dir` on `<html>` too, so the page's own layout flips with Candor's.
- `timeZone?: string | undefined`: An IANA time zone such as "Europe/Paris". With `locale`, server and browser format dates the same way, so absolute times render on the server too.
- `strings?: StringOverrides | undefined`: Overrides by component name, e.g. `{ thread: { jumpToLatest: "…" } }`; any left out stay English. Nested providers merge.
- `children: React.ReactNode`

## Functions

### directionOf

```ts
directionOf(locale: string | undefined)
```

"rtl" for Arabic, Hebrew, Persian, Urdu and the like; "ltr" otherwise.

### fill

```ts
fill(template: string, vars: Record<string, string | number> | undefined, number: (n: number) => string = String)
```

Fill `{name}` placeholders. Numbers go through the locale's number format.

### pick

```ts
pick(message: Phrase, count: number | undefined, rules: Intl.PluralRules)
```

The plural form for `count`, falling back to `other`.

### place

```ts
place(text: string, nodes: Record<string, React.ReactNode>)
```

A translated sentence with elements in it: `place(t("signedInAs"), { email: <strong>{email}</strong> })`. Each `{name}` becomes that node, so translators can move it within the sentence and it keeps its styling.

### useLocale

```ts
useLocale()
```

The locale, time zone and text direction in effect.

### useFormat

```ts
useFormat()
```

Locale-aware formatting that's safe to render on the server. With no `locale` set, numbers use "en-US" until the page hydrates and then the browser's locale; dates show their ISO form until then. Set `locale` (and `timeZone` for dates) on the provider to render the final text on the server.

### useHydrated

```ts
useHydrated()
```

False while rendering on the server and during React's hydration pass, true after. Anything that depends on the browser (its locale, its time zone) waits for true, so server and browser render the same markup.

### useStrings

```ts
useStrings(name: string, defaults: T)
```

A component's strings: `t("jumpToLatest")`, `t("remove", { name })`, `t("files", { count: 3 })`. `name` is the component's registry name in camelCase, the key apps override it under; `defaults` is its English table.

### useStartSide

```ts
useStartSide()
```

Physical side for the reading direction: "left" in LTR, "right" in RTL. For the few places CSS can't say start.

## Types

### Phrase

One string, or its plural forms chosen by `count`; `{name}` placeholders are filled in.

```ts
string | ({ other: string } & Partial<Record<Intl.LDMLPluralRule, string>>)
```

### StringTable

A component's strings: its English defaults, or your overrides.

```ts
Record<string, Phrase>
```

### StringOverrides

Overrides by component name, e.g. `{ thread: { jumpToLatest: "…" }, composer: { send: "…" } }`.

```ts
Record<string, Partial<StringTable> | undefined>
```

### TextDirection

```ts
"ltr" | "rtl"
```

### DateStyle

How an absolute time reads: "5 Nov 2026", "5 Nov 2026, 14:03" or "14:03".

```ts
"date" | "datetime" | "time"
```

### Formatter

```ts
{ /** The locale actually used right now; "en-US" before hydration when no locale is set. */ tag: string | undefined /** A number in the locale: "1,234.5", "1 234,5". */ number: (n: number, options?: Intl.NumberFormatOptions) => string /** An absolute time. Until the page hydrates it reads as an ISO date in UTC, unless both `locale` and `timeZone` are set. */ date: (date: Date | string | number, s
```

### Translate

Looks up one string by key and fills its placeholders; `count` also picks the plural form.

```ts
(key: keyof T & string, vars?: Record<string, string | number>) => string
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
