Browse docs

Core

Translating & Formatting

ReactVanilla

Everything in this page is plain @secundus-studio/stift-core — no adapter needed. The React hooks (Translating & Values) are thin wrappers over these exact methods.

Calling t

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, ICU Select & Plural.

Params: declared vs undeclared

  • Declared in params.context (stift.config.ts) → 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:

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.

Formatting: @secundus-studio/stift-format

The useFormatter hooks wrap @secundus-studio/stift-format — import it directly:

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.

Units without React

The unit state lives on the headless instance (full rules: Units):

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:

import { formatUnit } from '@secundus-studio/stift-format'

formatUnit(1000, { locale: Stift.getLocale(), unit: Stift.getUnits().length ?? 'meter' })

Next