# Fill strings

The text's first letter in upper case, as the locale writes it: "Environment" from "environment", "İstanbul" from "istanbul" in Turkish. The first grapheme, so a letter with combining marks stays whole. For headings and the start of a sentence.

- 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/candor/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

### startOf

```ts
startOf(text: string, locale?: string)
```

### fill

```ts
fill(template: string, vars: Vars | undefined, number: (n: number) => string = String, nouns?: NounTable, locale?: string)
```

Fill `{name}` placeholders: from `vars` first, then from the app's `nouns` (`{environment}` in the form the message's `count` picks, else its `one` form). `{Environment}` reads `vars.Environment`, else `startOf(vars.environment)`, so a value given for the noun also starts a sentence; with neither, a noun named `Environment`, else the noun's `start`, else `startOf` it. A placeholder with none of these stays as written. Numbers go through `number`, the locale's number format in a component.

### pick

```ts
pick(message: Phrase, count: number | undefined, rules: Intl.PluralRules, nouns?: NounTable, vars?: Vars)
```

The form of a message for `count`: plural forms fall back to `other`; a `SelectPhrase` takes its case from `vars[select]`, else from the `gender` of the noun called `select`, else `other`.

### singularNouns

```ts
singularNouns(nouns: NounTable)
```

Each noun in its singular (or only) form.

### englishStrings

```ts
englishStrings(table: T, options?: { /** The app's nouns, over Candor's own (`nounDefaults`), for `{environment}` and the like. */ nouns?: Partial<NounTable> | undefined; })
```

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, the app's nouns among them, and picks plural and select 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"
englishStrings(table, { nouns: { environment: "workspace" } })
```

### withNouns

```ts
withNouns(base: NounTable, nouns: Partial<NounTable> | undefined)
```

`nouns` over `base`, noun by noun; a noun left out (or `undefined`) keeps the one below. An `environment` with an `other` form and no `environments` beside it gives `environments` that form, so `{environments}` follows the app's plural; an `environments` the app sets wins.

## Values

### nounDefaults

Candor's own nouns, which the app's replace one by one.

```ts
const nounDefaults: {
  environment: {
    one: string;
    other: string;
  };
  environments: string;
  account: {
    one: string;
    other: string;
  };
  product: string;
}
```

## Types

### PluralPhrase

A message's plural forms, chosen by its `count`; `other` is the one every language has.

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

### SelectPhrase

A message's forms chosen by a word: `vars[select]` when it names a case, else the `gender` of the noun called `select`, else `other`. Each case is a string or plural forms:

```
{ select: "environment", cases: { feminine: "Nouvelle {environment}", other: "Nouvel {environment}" } }
```

```ts
{ select: string; cases: { other: string | PluralPhrase } & Record<string, string | PluralPhrase> }
```

### Phrase

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

```ts
string | PluralPhrase | SelectPhrase
```

### Noun

One of the app's nouns: a word ("workspace"), or its plural forms (`{ one: "workspace", other: "workspaces" }`), with a `gender` for messages that select on it and a `start` for the start of a sentence where upper-casing the first letter isn't how the language does it.

```ts
string | (PluralPhrase & { gender?: string | undefined; start?: string | PluralPhrase | undefined })
```

### NounTable

The app's nouns by name; a message names one as `{environment}`, or `{Environment}` at the start of a sentence.

```ts
Record<string, Noun>
```

### Nouns

Each noun in its singular (or only) form, for the app's own code: what `useNouns()` gives.

```ts
{
  /** One place agents act in: "environment", or the app's "workspace", "project". */
  environment: string;
  /** More than one of them: the app's own word, else the `other` form of the app's `environment`. */
  environments: string;
  /** The paying customer that owns the environments. */
  account: string;
  /** The product's own name, for labels like "In the app" and "What the app will read". */
  product: string;
} & Record<string, 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`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
