---
id: vanilla/translating
title: Translating & Formatting
---

Everything in this page is plain `@secundus-studio/stift-core` — no adapter needed. The React hooks ([Translating & Values](../react/translating.md)) are thin wrappers over these exact methods.

## Calling `t`

```ts
Stift.t('checkout', 'title')                 // unscoped: namespace + key
Stift.scope('checkout')('title')             // scoped: bare keys
Stift.scope(['common', 'checkout'])('login') // multi: first namespace with the key wins
Stift.t('checkout', 'title', { count: 3 }, 'ar') // render for a locale without switching
```

Keys, param shapes, and locales are literal types once the generator emits `stift.gen.d.ts` — typos and wrong params are compile errors. A cache miss returns the raw key, records it in `store.missingKeys`, reports `hooks.onError({ code: 'missing-key' })`, and kicks a background load so the next read resolves. ICU select/plural/offset work through the default engine; swap it via `createStift({ formatter })`. Authoring the messages: [Messages & Keys](../authoring/messages.md), [ICU Select & Plural](../authoring/icu-select-plural.md).

## Params: declared vs undeclared

- **Declared** in `params.context` ([stift.config.ts](../configuration/config-file.md)) → **optional at call sites**. Supply them once via `Stift.setParam(name, value)` (validated against the declared `values`) — every `t()` call auto-merges them.
- **Undeclared** → **always required** at the call site. There is no strictness dial.

Per-locale defaults (`defaults: { all: …, ar: … }`) are recomputed whenever the active locale switches and inherit down the dialect chain.

## Rich text: the headless pattern

Tag rendering is adapter territory (React's `t.rich` walks the AST for you), but the AST itself is public core API — vanilla consumers walk the same tree:

```ts
import { parseMessage } from '@secundus-studio/stift-core'

const { ast } = parseMessage(stift.getMessage('common', 'rich.demo'))
// walk ast: string nodes concatenate; tag nodes yield { tagName, children }
// so you can build DOM nodes, terminal spans, or an email-safe string
```

Use `stift.getMessage(ns, key)` for the untranslated source of a message; format the plain-text version with `t()` as usual. Markdown messages follow the same shape (`t.markdown` in React is just a renderer over it). Full signatures: [Translating](./api/translate.md).

## Formatting: @secundus-studio/stift-format

The `useFormatter` hooks wrap `@secundus-studio/stift-format` — import it directly:

```ts
import { formatNumber, formatCurrency, formatDate, formatList, formatUnit } from '@secundus-studio/stift-format'

formatNumber(1234.5, { locale: 'fr' })                    // "1 234,5"
formatCurrency(9.99, { locale: 'en', currency: 'EUR' })   // "€9.99"
formatUnit(12000, { locale: 'en', unit: 'kilometer' })    // "12 km"
convertUnit(12000, 'meter', 'kilometer')                  // 12
```

All formatters take the locale explicitly — pass `Stift.getLocale()` to stay in sync with the active locale. Locale switching: [Dialects](../authoring/dialects.md).

## Units without React

The unit *state* lives on the headless instance (full rules: [Units](../react/units.md)):

```ts
Stift.setUnit('length', 'mile')   // validated against config supportedValues
Stift.getUnits()                  // { length: 'mile', … } effective per-category record
Stift.resetUnit('length')         // back to the per-locale default
Stift.getUnitValues('length')     // allowed units for a category
```

Render with `formatUnit` + the active unit — values are always in the category base unit:

```ts
import { formatUnit } from '@secundus-studio/stift-format'

formatUnit(1000, { locale: Stift.getLocale(), unit: Stift.getUnits().length ?? 'meter' })
```

## Next

- Unit categories, base-unit contract, pickers → [Units](../react/units.md)
- Declaring params/units in config → [stift.config.ts](../configuration/config-file.md)
- Persistence and offline behavior → [Offline & Persistence](../runtime/offline-persistence.md)
