Skip to main content
The fastest path into astilba: install the published package, @astilba/core, and use its two most directly useful capabilities — the vendored CLDR plural rules and the MT masking utilities. These are the parts of the engine that work standalone, with no file-format adapter.

Prerequisites

  • Node.js >= 22.14
  • A package manager that resolves the @astilba/core package from npm (pnpm, npm, or yarn).
astilba is pre-1.0. These examples reflect the API as it ships today; the public API may change before 1.0.

Install

pnpm add @astilba/core
@astilba/core ships two entry points:
  • @astilba/core — the full API: the canonical model, AstilbaError, the harness contracts, and masking utilities.
  • @astilba/core/cldr — just the vendored CLDR plural rules.

Select a plural category

The CLDR table is vendored, not read from Intl.PluralRules, so it is identical on Node, Bun, Deno, browsers, and Workers:
import { selectCategory, categoriesFor } from "@astilba/core";

// English cardinal: two categories.
categoriesFor("en", "cardinal");
// → ["one", "other"]

// Which category does a count select?
selectCategory("en", 1, false); // → "one"
selectCategory("en", 2, false); // → "other"

// Russian has four cardinal categories.
selectCategory("ru", 21, false); // → "one"
selectCategory("ru", 3, false); //  → "few"
selectCategory("ru", 5, false); //  → "many"
Languages not in the vendored table fall back to other-only. You can check ahead of time:
import { isSupportedLanguage, SUPPORTED_LANGUAGES } from "@astilba/core";

isSupportedLanguage("en"); // → true
isSupportedLanguage("xh"); // → false (falls back to "other"-only)

SUPPORTED_LANGUAGES; // → ["ar", "de", "en", "fr", "ja", "ko", "pl", "ru", "zh"]
See CLDR plural rules for why the table is vendored.

Mask a value before machine translation

Before you send a string to a machine-translation engine, mask everything that must not be translated — interpolation variables, formatter keywords, $t() nesting refs, and markup. maskTokens replaces each non-text token with an opaque sentinel; unmask restores it. maskTokens works on the canonical token view. If you have a raw i18next value string and want to tokenize it, you currently need an adapter’s tokenizer; from pure core you can mask a hand-built token array directly:
import { maskTokens, unmask } from "@astilba/core";

const tokens = [
  { type: "text", raw: "Hello, " },
  { type: "interpolation", raw: "{{name}}", variable: "name" },
  { type: "text", raw: "!" },
] as const;

const { masked, parts } = maskTokens(tokens);
// masked → "Hello, \uE0000\uE001!"   (the variable is a private-use-area sentinel)

const back = unmask(translated, parts); // restore after MT returns
After translation comes back, validate the placeholders survived unmodified — see MT masking.

Where to go next

  • To round-trip i18next resource files (parse → export → render), you need the i18next-v4 adapter, which lives in the repo but is not yet published. See the adapter reference.
  • For the full @astilba/core export surface, see the core API reference.
Need help, or found something that looks wrong? Open an issue on GitHub.