Skip to content
lingo

API

Functions, options, issue codes, and type safety.

Prefer the live demos? Open this section in the interactive docs.

Core exports from @pascal-app/lingo:

  • lingo(text, opts?)
  • parseQuantity(text, opts?)
  • parseRange(text, opts?)
  • partialState(text, opts?)
  • findQuantities(text, opts?)
  • quantity(value, unitRef, kind?)
  • convert(value, from, to)
  • tryConvert(value, from, to) (non-throwing, returns structured issues)
  • convertDelta(value, from, to)
  • convertCurrency(amount, from, to, { rates }) (literal currency refs typed)
  • fromMinor(amount, currency) (literal currency refs typed)
  • fromJSON(json)
  • describeResource(quantityOrRange) and describeResult(result) from @pascal-app/lingo/describe for rich unit labels, formatted output, direct value resources, source text spans, and resource-style parse-result output for quantity/range/conversion/number/date/duration results
  • registerKind(def), registerUnits(kind, units), defineFuzzyVocab(kind, vocab), createRegistry(kinds), createLingo(opts)
  • describeTemperature(quantity, opts?)

Options:

  • kind: Bias unit resolution and restrict expected kind.
  • unit: Assume a unit for bare numbers and set canonical field unit.
  • currency: Disambiguate bare currency symbols and ambiguous minor-unit words such as $, ¥, and cents; explicit GBP pence forms such as 50p and 3 quid 50 parse as GBP.
  • system: Choose US, imperial, or metric unit families.
  • numberFormat: Resolve ambiguous decimal and grouping separators.
  • locale: Select a loaded language profile; omit for auto-detection among loaded packs plus English.
  • strictness: forgiving, confirm, or strict.
  • accept: Switch ranges, conversions, compounds, fuzzy, numberWords, approximations, bareNumbers.
  • tolerance: typos fix/suggest/off and ambiguity assume/confirm.
  • escalate: Map issue codes to error, warning, or info.
  • messages: Override human copy by issue code.

Parse results are versioned discriminated unions: { schemaVersion: 3, ok, type, text, issues }; successes add span, confidence, and the parsed value, while failures use type: "failure" and may include a full candidate result. Success alternatives are also discriminated (type: "quantity" or type: "date"). Built-in createLingo() instances keep the same literal-unit checks as top-level quantity/convert/tryConvert; custom registry instances keep broad string refs. tryConvert() mirrors absolute convert() but returns { ok: true, type: "conversion", value, unit, kind } or { ok: false, type: "failure", issues } instead of throwing. Quantity instances expose .value, .base, .unit, .kind, .to(unitRef), .valueIn(unitRef), .convertDelta(unitRef), .toMinor() for currencies, .format(opts?), .toBest(opts?), and .toJSON(). Format defaults are parseable; localizedUnits: true is display-only for Intl unit words. Quantity JSON is self-describing: { schemaVersion: 3, type, kind, value, unit, base, baseUnit }; @pascal-app/lingo/describe adds unit labels and formatted strings for values, describeResource() returns direct lingo.quantity / lingo.range resources with grouped value and canonical amounts, and describeResult() returns an opt-in resource-style parse-result view with object, resourceSchemaVersion, grouped value/canonical amounts, range canonicalUnit, source text spans including full-input failure spans, rich issues, alternatives, candidates, conversion { source, target: { unit }, converted } data, lingo.date { value: { iso, epochMilliseconds }, calendar, grain, known }, and lingo.duration { value, canonical, formatted, parts? }. Quantity ranges expose .minBase, .maxBase, .min(), .max(), .plusMinus, .fuzzy, .contains(q), .widthIn(unitRef), .to(unitRef), and .format().

Issues are { code, severity, message, span, suggestions?, data? }; parse-path spans are { start, end } half-open offsets into the original input. Issue codes documented by the package: EMPTY, NO_VALUE, UNKNOWN_UNIT, KIND_MISMATCH, RANGE_KIND_MISMATCH, CONVERSION_KIND_MISMATCH, RATE_REQUIRED, TRAILING_INPUT, SINGLE_VALUE_EXPECTED, APPROX_NOT_ALLOWED, UNIT_REQUIRED, CONVERSION_NOT_ALLOWED, NUMBER_FORMAT, NONFINITE, LOCALE_NOT_LOADED, RANGE_MIN, RANGE_MAX, RANGE_OPEN_BOUND_NOT_ALLOWED, REQUIRED, UNSUPPORTED_DATE, NOW_REQUIRED, TYPO_CORRECTED, AMBIGUOUS_NUMBER, AMBIGUOUS_UNIT, AMBIGUOUS_DATE, RANGE_REVERSED, COMPOUND_OVERFLOW, CIVIL_AVERAGE, UNIT_ASSUMED, WEEKDAY_ASSUMED_NEXT, SLANG_UNIT, TZ_IGNORED, AMBIGUOUS_TIMEZONE.

Data schemas

Every result serializes to a flat, versioned shape (schemaVersion: 3) — what JSON.stringify(result), toJSON(), and the /ai fields emit and re-parse. Every span is a half-open [start, end) range into the original input.

Result

// lingo() / parse*() return a discriminated union on `type`, serialized
// flat (v3). JSON.stringify(result) or result.toJSON() emits this shape.

interface Result {              // success — ok: true
  schemaVersion: 3
  ok: true
  type: "quantity" | "range" | "conversion" | "number"
  text: string                 // the original input
  span: Span                   // the slice the parse consumed
  confidence: number           // 0 to 1
  issues: Issue[]              // warnings and infos ride along on success
  // ...plus the value fields for its `type` (see the Quantity / Range tabs)
}

interface Failure {            // ok: false
  schemaVersion: 3
  ok: false
  type: "failure"
  text: string
  issues: Issue[]              // at least one has severity: "error"
  candidate?: Result           // "what it would have been", when recoverable
}

Quantity

// One value in one unit, stored canonically as `base`. Quantity.toJSON():
interface QuantityJSON {
  schemaVersion: 3
  type: "quantity"
  kind: Kind                   // "length" | "mass" | "currency" | ...
  value: number                // amount in `unit` (the 72 in "72 in")
  unit: string                 // "in"
  base: number                 // canonical amount (1.8288) — the source of truth
  baseUnit: string             // the kind's SI-anchored base ("m")
  parts?: { value: number; unit: string }[]   // compound input: "5 ft 11 in"
  approximate?: boolean        // "about 5 kg"
}

Range

// A min/max, plus-or-minus, or fuzzy range. QuantityRange.toJSON():
interface QuantityRangeJSON {
  schemaVersion: 3
  type: "range"
  kind: Kind
  baseUnit: string
  min?: { value: number; unit: string; base: number; exclusive?: boolean }
  max?: { value: number; unit: string; base: number; exclusive?: boolean }
  plusMinus?: {                // "10 ± 0.5 mm"
    center: { value: number; unit: string; base: number }
    delta: { value: number; unit: string; base: number }
  }
  fuzzy?: { term: string; profile: string }   // { term: "hot", profile: "weather" }
  approximate?: boolean        // "a few minutes"
}

Issue & span

// The one error / warning / info shape on every result's issues[].
interface Issue {
  code: IssueCode              // stable SCREAMING_SNAKE, e.g. "UNKNOWN_UNIT"
  severity: "error" | "warning" | "info"
  message: string              // human copy — override with the `messages` option
  span?: Span                  // where in the input; absent for /ai bound issues
  suggestions?: string[]       // did-you-mean, most likely first (max 3)
  data?: object                // code-specific payload, typed via IssueDataMap
}

// A half-open [start, end) character range into the ORIGINAL input string.
interface Span {
  start: number
  end: number
  text: string                 // input.slice(start, end) — reads for itself
}

Type safety

Unit refs are literal-typed from the registry, so cross-kind and unknown-unit mistakes are compile errors at zero runtime cost. Dynamic strings still work as the escape hatch, validated at runtime.

import { convert, quantity } from "@pascal-app/lingo"

convert(5, "in", "cm")    // ✅ number
quantity(5, "kg")         // ✅ Quantity<"mass"> — kind inferred from the unit

// @ts-expect-error 'kg' is mass, 'cm' is length — caught before you run
convert(5, "kg", "cm")
// @ts-expect-error 'nope' isn't a unit
quantity(5, "nope")

const u: string = userInput
quantity(5, u)            // ✅ dynamic strings still compile (validated at runtime)