Skip to content
lingo

Make forms easier,LLM tools safer.

Use one zero-dependency parser for user-entered quantities, dates, ranges, and model tool arguments. Store typed values, canonical units, spans, and warnings.

pnpm add @pascal-app/lingo

1.80 m

Height normalized to meters

5 GB

File size slang normalized

62.137 mi

Explicit conversion to miles

< 10 min

Open-ended duration range

Needs review

Relative date from today

Try kg

Typo found: use kg

85%

Percent shorthand normalized

20–28°C

Fuzzy language becomes a range

What people type, what you store

These rows are parsed by the real library when this page builds — the same engine that runs in the hero above. Every result carries the original-input span and stable issue codes.

They typeReads asYou store (canonical)
180cm180 cm
1.8 m
5'11"5′11″
1.8034 m
72 in to cm182.88 cm
182.88 cm
2 lb 3 oz2 lb 3 oz
0.992233 kg
an hour and a half1.5 h
5400 s
between 5 and 10 kg5–10 kg
5–10 kg
1,5 kg1.5 kg
1.5 kgseparator policy, not locale sniffing
it's hot27–35°C
300.15–308.15 Kfuzzy vocab is opt-in per field
5 meterz5 m
5 mTYPO_CORRECTEDdid-you-mean, absorbed as a warning

Ranges, conversions, tolerances, fuzzy words, typos, and nine languages of number words — see everything the parser reads in Parse and the full unit catalog.

For the web. For LLMs.

One parser powers both sides: the same schema validates a human form and an LLM tool call.

Make forms easier
  • One text field replaces a value box plus a unit dropdown — accepts how people actually type, typos included.
  • Fields are never yelled at mid-typing: 2 f is incomplete, not invalid. Issues carry stable codes, spans, and did-you-mean suggestions.
  • Headless everywhere: DOM controller, <lingo-input> web component, React hook, React Native hook. No styles shipped.
  • Two-way guarantee: 1.9999 m formats as 6′7″, never 5′12″ — and re-parses to the same value.
Form inputs →
Make LLM tools safer
  • Models are better at emitting "5'11"" than 1.8034. Standard Schema fields make the string the reliable path.
  • Tool-boundary defaults fail loudly on ambiguous numbers, ignored timezones, and clock-drifting relative dates — with candidates a model can self-correct from.
  • repairToolCallWith fixes malformed payloads client-side, with no extra model round trip.
  • Deterministic and replayable: an explicit now means a queued or retried tool call never drifts across midnight.
Tool fields →

The tool boundary, before and after

Constrained decoding makes JSON parse. lingo checks whether the values mean what they should — and hands back canonical numbers your database can trust.

json/model-output.json
// What the model emits (strings, like people write)
{
  "weight": "2 lbs",
  "height": "5'11\"",
  "deliverBy": "next tuesday"
}
json/canonical.json
// After canonicalizeValues(args, spec)
{
  "weight": 0.90718474,   // kg, exact legal factor
  "height": 1.8034,       // m
  "deliverBy": "2026-07-28T…Z"  // ISO, from an explicit now
}
ts/shipment-tool.ts
import { lingoObject, quantityField, dateField } from '@pascal-app/lingo/ai'
import { tool } from 'ai'

const shipment = lingoObject({
  weight: quantityField({ kind: 'mass', unit: 'kg', min: 0 }),
  deliverBy: dateField(), // relative dates require an explicit now
})

// AI SDK, OpenAI strict, Anthropic, Gemini, LangChain, MCP — one schema
tool({ inputSchema: shipment, execute: run })
tsx/gen-ui-form.tsx
// Generative UI: let the model assemble the form,
// let lingo own every quantity/date field it renders.
const field = useLingoInput({ kind: 'mass', unit: 'kg', name: 'weight_kg' })
// agent or human types "2 lbs" -> the form submits weight_kg=0.90718474

The same schema drops into the Vercel AI SDK, OpenAI strict tools, Anthropic, Gemini, LangChain, and MCP — see one schema for the form-and-tool pairing.

Frequently asked questions

Short answers, with the docs section to go deeper.

How do I parse "5'11\"" into meters in JavaScript?

parseQuantity("5'11\"", { kind: "length" }).quantity.to("m").value returns 1.8034. lingo reads compounds (5'11", 2 lb 3 oz, 1h30), unicode (½, μm, ′ ″), number words, and typos with did-you-mean — zero dependencies.

Read more →
How do I validate LLM tool-call arguments like "2kg" or "next Friday"?

Declare tool inputs with quantityField / dateField from @pascal-app/lingo/ai. Models emit natural-language strings; lingo canonicalizes them at the tool boundary and fails loudly on ambiguity with a machine-actionable candidate instead of a silent guess.

Read more →
Can one text input replace a value box plus a unit dropdown?

Yes — that is the core form pattern. A headless DOM controller, <lingo-input> web component, React hook, and React Native hook accept "180cm" or "5ft 11" and submit one hidden canonical value, with spans and stable issue codes for validation UX.

Read more →
Does lingo have any runtime dependencies?

Zero. Every entry is tree-shakeable, size budgets are enforced in CI, and Intl (built into every runtime) handles locale-aware number formatting.

Read more →
Which languages can lingo parse?

English is built in; opt-in locale packs add Spanish, French, Portuguese, Chinese, Japanese, and en-GB parsing. Packs are data-only subpath imports, so you ship only the languages you load.

Read more →
Is parsing deterministic enough for agents and queued jobs?

Yes. Reference time is always an explicit now option (never Date.now() inside parsing), same input plus options gives the same output, and everything format() emits re-parses to the same value.

Read more →