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)anddescribeResult(result)from@pascal-app/lingo/describefor rich unit labels, formatted output, direct value resources, source text spans, and resource-style parse-result output for quantity/range/conversion/number/date/duration resultsregisterKind(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$,¥, andcents; explicit GBP pence forms such as50pand3 quid 50parse 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)