# Fill strings

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

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

## Install

```bash
npx candor-ui add fill-strings
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/fill-strings`.
It brings in: tokens.

## Example

```tsx
"use client"

import { englishStrings } from "@/lib/fill-strings"

// A helper that names a tool outside any component (on the server, or in an adapter's toolTitle) reads its table
// with englishStrings; in a component, pass useStrings' translator instead, so the app's words apply.
const toolStrings = {
  toolStopRun: "Stop {run}",
  theRun: "the run",
  steps: { one: "{count} step", other: "{count} steps" },
}

const t = englishStrings(toolStrings)

export default function Example() {
  return (
    <ul className="flex flex-col gap-1 text-sm">
      <li>{t("toolStopRun", { run: "run-7" })}</li>
      <li>{t("toolStopRun", { run: t("theRun") })}</li>
      <li>{t("steps", { count: 1 })}</li>
      <li>{t("steps", { count: 12 })}</li>
    </ul>
  )
}
```

## Functions

### fill

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

### pick

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

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

### englishStrings

```ts
englishStrings(table: T)
```

A translate function over a table of English defaults, for code that runs outside a component: on the server, or a helper called without the component's `useStrings`. It fills placeholders and picks plural forms as `useStrings` does with no overrides, in English. This module has no `"use client"`, so a server module can import it.

```
const t = englishStrings(dataTableStrings)
t("selected", { count: 3 }) // "3 selected"
```

## Types from other items

The props above take these. Each is defined by another item; import it from the path shown.

### Message

Import: `import type { Message } from "@/lib/chat"` (item `chat`, https://candor.themethodic.com/docs/chat.md)

```ts
{
  id: string;
  author: MessageAuthor;
  /** When it was sent. Absent when the source doesn't say; then no time is shown. */
  createdAt?: string | undefined;
  status: MessageStatus;
  blocks: MessageBlock[];
  reasoning?: ReasoningEntry[] | undefined;
  /** How long the agent spent before answering, e.g. "6s". Only once known. */
  thoughtFor?: string | undefined;
  /**
   * When the request for this reply went out, as the app measured it: an ISO time or milliseconds since the epoch.
   * While the reply streams and nothing of it has arrived, its working line counts up from here ("Thinking · 4.2s"),
   * and so does its reasoning's. Left out, no time is shown.
   */
  startedAt?: string | number | undefined;
  sources?: MessageSource[] | undefined;
  /** Shown under an agent's reply: model, cost. Only values the service measured. */
  attribution?: {
    model?: string | undefined;
    cost?: string | undefined;
  } | undefined;
}
```

### MessageAuthor

Import: `import type { MessageAuthor } from "@/lib/chat"` (item `chat`, https://candor.themethodic.com/docs/chat.md)

```ts
{
  role: "user";
  name?: string | undefined;
} | {
  role: "agent";
  agent: AgentIdentity;
}
/** A person on the app's own team (support staff, a reviewer), not the user: shown by name, with their team. */
 | {
  role: "person";
  name: string;
  team?: string | undefined;
}
/** System lines are the app's own strings, never model text. */
 | {
  role: "system";
}
```

### MessageStatus

Import: `import type { MessageStatus } from "@/lib/chat"` (item `chat`, https://candor.themethodic.com/docs/chat.md)

```ts
"streaming" | "complete" | "interrupted" | "failed"
```

### MessageBlock

Import: `import type { MessageBlock } from "@/lib/chat"` (item `chat`, https://candor.themethodic.com/docs/chat.md)

```ts
{
  kind: "paragraph";
  nodes: InlineNode[];
}
/**
 * Text exactly as someone typed it: line breaks and spacing kept, nothing parsed. For a person's own words, so
 * `*this*` stays `*this*` and a pasted list keeps its lines.
 */
 | {
  kind: "plain";
  text: string;
}
/** A heading inside a reply. Levels are relative to the message, 1 the largest; the page's own headings sit above. */
 | {
  kind: "heading";
  level: 1 | 2 | 3;
  nodes: InlineNode[];
} | {
  kind: "list";
  ordered?: boolean | undefined;
  items: InlineNode[][];
} | {
  kind: "payload";
  language: PayloadLanguage;
  text: string;
  title?: string | undefined;
} | {
  kind: "tools";
  calls: ToolCall[];
}
/** Something the agent could not do, and what is missing because of it. */
 | {
  kind: "degraded";
  reason: string;
  missing?: string | undefined;
}
/**
 * A typed card (a result table, a plan, an approval) rendered by the app's
 * card registry. `summary` is the service's plain-text version, used for copy
 * and whenever the card can't be shown.
 */
 | {
  kind: "card";
  card: string;
  props: unknown;
  summary: string;
}
/** Files sent with the message, shown as tiles. New in 1.1: a `switch` over kinds needs a `default` branch. */
 | {
  kind: "attachments";
  files: Attachment[];
}
```

### ReasoningEntry

Import: `import type { ReasoningEntry } from "@/lib/chat"` (item `chat`, https://candor.themethodic.com/docs/chat.md)

```ts
| (ReasoningBase & { basis: "verified"; evidence: string })
  | (ReasoningBase & { basis: "believed" | "assumed"; assumption: string })
```

### MessageSource

Import: `import type { MessageSource } from "@/lib/chat"` (item `chat`, https://candor.themethodic.com/docs/chat.md)

```ts
{
  id: string;
  /** The [n] shown inline. Assigned by the service, so it is stable across renders. */
  marker: number;
  kind: SourceKind;
  label: string;
  /** An inert excerpt shown in the popover. Text only. */
  snippet?: string | undefined;
  /** Where it came from in a human sense: "Docs · Sales process v3", "Query on orders". */
  origin?: string | undefined;
  provisional?: boolean | undefined;
}
```

## 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`.
