# lingo > Make forms easier, LLM tools safer. Zero-dependency TypeScript library that parses natural-language quantities, units, dates, and ranges ("2 ft", "5'11\"", "72 in to cm", "between 5 and 10 kg", "three days ago", "it's hot") into canonical SI-anchored values; converts, validates, formats, and humanizes. Agent fetch order (online): `https://lingo.pascal.app/llms.txt` (index) → `https://lingo.pascal.app/docs/
.md` (per-topic) or `https://lingo.pascal.app/llms-full.txt` (complete narrative). Offline: this file (`node_modules/@pascal-app/lingo/llms.txt`) is the compressed self-contained reference. Keep user measurements as strings in tool schemas; call lingo to convert, validate, surface spans, and handle ambiguity. Entries: `@pascal-app/lingo` (core+units+fuzzy), `@pascal-app/lingo/date`, `@pascal-app/lingo/dom`, `@pascal-app/lingo/element` (``), `@pascal-app/lingo/react`, `@pascal-app/lingo/react-native`, `@pascal-app/lingo/ai` (LLM tool fields), `@pascal-app/lingo/mcp` (MCP tool helper), `@pascal-app/lingo/describe` (rich value/result descriptions), `@pascal-app/lingo/catalog` (query units/kinds/currencies + ISO country codes), `@pascal-app/lingo/complete` (ranked autocomplete completions), `@pascal-app/lingo/schema` (JSON Schema + OpenAPI + enum reference), `@pascal-app/lingo/locales/{en,en-gb,es,fr,pt,zh,ja}` (tree-shakeable language packs), `@pascal-app/lingo/core`. ## Core (`@pascal-app/lingo`) ```ts import { lingo, parseQuantity, parseRange } from "@pascal-app/lingo" const height = parseQuantity("5'11\"", { kind: "length" }) if (height.ok) height.quantity.to("m").value // 1.8034 const conversion = lingo("72 in to cm") // converted 182.88 cm const range = parseRange("between 5 and 10 kg", { kind: "mass" }) ``` - `lingo(text, opts?) → LingoResult`: versioned union on `.type`: 'quantity' | 'range' | 'conversion' | 'number' | 'failure'. Serialized as FLAT v3 JSON: `{ schemaVersion: 3, ok, type, ...value fields at top..., text, span, issues, confidence }`. - `parseQuantity(text, opts?)`, `parseRange(text, opts?)`, `partialState(text, opts?)`, `findQuantities(text, opts?)`. - `quantity(value, unitRef, kind?)`, `convert(v, from, to)`, `tryConvert(v, from, to)`, `convertDelta(v, from, to)`. - `createLingo({ registry?, kinds?, messages?, fuzzy?, locales? }) → instance` for SSR/multi-tenant isolation and loaded locale packs. - `fromJSON(json)` rehydrates `toJSON()` output. - Result guards: `isQuantity`, `isRange`, `isConversion`, `isNumber`; helpers: `firstError`, `candidateOf`, `formatIssue`. Options `{ kind?, unit?, currency?, locale?, system?: 'us'|'imperial'|'metric', numberFormat?: 'auto'|'dot-decimal'|'comma-decimal', strictness?: 'forgiving'|'confirm'|'strict', accept?, tolerance?, escalate?, messages?, profile?, registry? }`. Quantity: `.value`, `.base`, `.kind`, `.to(unitRef)`, `.valueIn(u)`, `.convertDelta(u)`, `.toMinor()`, `.format()`, `.toBest()`, `.toJSON()`. Literal `.to()` targets are same-kind checked at compile time; dynamic strings still validate at runtime. QuantityRange: `.minBase/.maxBase/.min()/.max()/.plusMinus/.fuzzy/.contains(q)/.widthIn(u)/.to(u)/.format()`. Literal `.to()` targets get the same check. Kinds: length mass temperature duration volume area speed data data_rate flow_rate acceleration pressure energy force torque power frequency angle percent luminous_intensity luminous_flux illuminance luminance voltage current resistance charge substance concentration radiation_absorbed_dose radiation_equivalent_dose radioactivity currency. Bases: m, kg, K, s, m³, m², m/s, byte, bit/s, m³/s, m/s², Pa, J, N, N⋅m, W, Hz, rad, %, cd, lm, lx, cd/m², V, A, Ω, C, mol, mol/m³, Gy, Sv, Bq, and self-canonical ISO currency codes. ## Complete (`@pascal-app/lingo/complete`) ```ts import { completions } from "@pascal-app/lingo/complete" const items = completions("10 kg to 16", { kind: "mass", limit: 8 }) // [{ text, result, confidence, source }] — source: parse|alternative|unit-ambiguity|unit-prefix|implied-unit|range-implied|cross-kind|date ``` Distinct from success `alternatives`, failure `candidate`, and issue `suggestions`. Wire into `lingoInput({ complete, onComplete })` or `useLingoInput({ complete })` for autocomplete dropdowns. The React hook exposes `completions`, `highlightedIndex`, `setHighlightedIndex()`, and `selectCompletion()` while keeping popup UI caller-owned. Pass `date: (text) => parseDate(text, { now })` to opt into date completions without bundling `@pascal-app/lingo/date` into `@pascal-app/lingo/complete`. ## Locales (`@pascal-app/lingo/locales/*`) ```ts import { createLingo } from "@pascal-app/lingo" import { es } from "@pascal-app/lingo/locales/es" import { fr } from "@pascal-app/lingo/locales/fr" import { zh } from "@pascal-app/lingo/locales/zh" const lingo = createLingo({ locales: [es, fr, zh] }) lingo.parseQuantity("dos kg") // auto-detected as es; result.locale === "es" lingo.parseRange("entre 5 et 10 kg", { locale: "fr" }) lingo.parseQuantity("5公斤", { locale: "zh" }) ``` Packs: `en`, `en-gb`, `es`, `fr`, `pt`, `zh`, `ja`. Omit `locale` to detect among loaded packs plus English; pass `locale` for known fields. English is built in and does not require a pack. `parseDate(text, { locale, localePacks, now })` can use loaded packs for relative date vocabulary. Pack notes: - `en`: built-in English quantity/date grammar; wave-1 adds spoken decimals and date idioms such as `two point five kg` and `quarter of five`. - `en-gb`: English plus day-first numeric dates and UK weekday offsets (`Monday week`, `Tuesday fortnight`). - `es`: Spanish number/range words, Romance composition, spoken decimals, approximants, and date clock/edge idioms. - `fr`: French number/range words, exhaustive CLDR-style vigesimal compounds, spoken decimals, approximants, and weekday-offset/clock idioms. - `pt`: Portuguese number/range words, compound hundreds/tens, spoken decimals, approximants, and clock/day-offset idioms. - `zh`: Chinese CJK number walker, scale grouping, post-unit half, duration counters, period edges, and day offsets. CJK spoken clock (`下午3点半`) is deferred. - `ja`: Japanese CJK number walker, scale grouping, post-unit half, duration counters, wave-dash ranges, and double-step period modifiers. CJK spoken clock (`午後3時半`) is deferred. Verified locale examples (fixed `now = 2026-07-03 14:30` local time for date examples): ```txt en: "two point five kg" → 2.5 kg; "quarter of five" → 2026-07-04 04:45 en-gb: "Monday week" → 2026-07-13; "Tuesday fortnight" → 2026-07-21 es: "treinta y cinco kilos" → 35 kg; "dos coma cinco kg" → 2.5 kg; "las tres menos cuarto" → 2026-07-04 02:45 fr: "quatre-vingt-dix kg" → 90 kg; "deux virgule cinq kg" → 2.5 kg; "deux heures et quart" → 2026-07-04 02:15; "lundi en huit" → 2026-07-13 pt: "vinte e cinco kg" → 25 kg; "dois virgula cinco kg" → 2.5 kg; "quinze para as tres" → 2026-07-04 02:45; "depois de amanha" → 2026-07-05 zh: "三十五公斤" → 35 kg; "两公斤半" → 2.5 kg; "七八天" → duration range 7..8 d; "月底" → 2026-07-31 ja: "三十五キロ" → 35 kg; "一時間半" → 1.5 h; "5〜10キロ" → 5..10 kg; "再来週" → week of 2026-07-13 ``` ## Dates (`@pascal-app/lingo/date`) ```ts import { parseDate, parseDateRange, humanizeDate, parseDuration } from "@pascal-app/lingo/date" const now = new Date("2026-07-08T12:00:00Z") parseDate("three days ago", { now }) // grain "day"; reference-dependent inputs need explicit now parseDateRange("2pm to 4pm", { now }) // { start, end } civil endpoints parseDuration("1h30").duration.base // 5400 seconds humanizeDate(d, { now }) // "3 days ago" — always re-parses within one grain ``` - `parseDate(text, { now?, dayFirst?, weekStart?, forwardDates?, applyZone?, locale?, strictness?, escalate?, messages? })`. - Trailing timezones detected on `.zone`; pass `applyZone: true` to resolve the real UTC instant. - `parseDateRange`, `humanizeDateRange`, `humanizeDuration`. ## DOM (`@pascal-app/lingo/dom`) ```ts import { lingoInput } from "@pascal-app/lingo/dom" const field = lingoInput(document.querySelector("#height"), { kind: "length", unit: "m", name: "height_m", }) field.set("6ft") field.commit() // hidden input submits canonical value ``` `field.value` · `.quantity` · `.result` · `.state` ('idle'|'incomplete'|'valid'|'invalid') · `.set()` · `.commit()` · `.update()` · `.destroy()`. Never rewrites while typing; canonicalizes on blur/Enter/submit. With `complete`/`onComplete`, the input gets the headless WAI-ARIA combobox attributes (`role="combobox"`, `aria-autocomplete="list"`, `aria-expanded`); pass `listboxId` to set `aria-controls` for your rendered listbox. ## React (`@pascal-app/lingo/react`) ```tsx import { completions } from "@pascal-app/lingo/complete" import { useLingoInput } from "@pascal-app/lingo/react" function HeightField() { const field = useLingoInput({ kind: "length", unit: "m", name: "height_m", listboxId: "height-options", complete: (text) => completions(text, { kind: "length" }), }) return ( <> ) } ``` - Attach `field.ref` to `` / `