bcp-47

Parse and stringify BCP 47 language tags into structured, spec-compliant schema objects.

Library
npm
v2.1.1
75stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
53/100Fair
Development Activity52
Maintenance52
Community36
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
82/100Excellent
Architecture85
Code Quality90
Innovation88
Learning Curve65

bcp-47 is a zero-heavyweight-dependency JavaScript library that parses BCP 47 (RFC 5646) language tags — the standard behind HTML lang attributes, HTTP Accept-Language headers, and Unicode locale identifiers — into a fully-typed schema object, and stringifies schema objects back into valid tags. It implements the complete BCP 47 grammar: extended language subtags, ISO 15924 scripts, ISO 3166-1/UN M49 regions, variants, singleton extensions, private-use subtags, and the fixed lists of grandfathered irregular and regular tags.

The parser supports a forgiving mode that returns partial results with warning callbacks instead of throwing, plus a default normalization mode that maps legacy forms (like i-klingon) to their modern equivalents. Written by Titus Wormer as part of the wooorm/unified family of text-processing utilities, it ships ESM-only with full TypeScript types and depends only on his own tiny is-alphabetical/is-alphanumerical/is-decimal character-class checkers.

What You Get

  • A parse(tag, options) function that turns any BCP 47 string into a structured schema with language, script, region, variants, extensions, and private-use fields
  • A stringify(schema) function that serializes a schema object back into a canonical BCP 47 tag string
  • Forgiving-mode parsing that returns partial matches with warning callbacks instead of throwing on malformed tags
  • Automatic normalization of legacy/irregular tags (e.g. i-klingon to tlh) per the BCP 47 registry
  • Full TypeScript type definitions for Schema, Extension, Options, and Warning

Common Use Cases

  • Validating and normalizing user-supplied Accept-Language header values in an HTTP server
  • Extracting the script/region/variant components of a document’s lang attribute for i18n routing
  • Linting or canonicalizing locale tags stored in a CMS or translation management system
  • Building higher-level locale-matching or negotiation logic on top of a reliable BCP 47 parser

Under The Hood

Architecture The core is two small files plus two static lookup tables: parse.js implements a hand-written character scanner over the lowercased tag string, threading a single index cursor through sequential grammar productions — language, then extended language subtags, script, region, variants, extensions, and private-use — with an early-exit fail() closure that either returns partial results (forgiving mode) or an empty schema. stringify.js is a pure inverse function that reads the same schema shape and joins its parts back into a tag string. regular.js and normal.js hold the fixed lists of grandfathered/legacy tags used for normalization. There is no class hierarchy, no external state, and no I/O — the whole library is stateless value transformation, so a change to the core parsing loop stays fully self-contained and is exercised directly by the fixtures under test/fixtures.

Tech Stack Plain ESM JavaScript (type: module) authored with JSDoc and compiled to a generated index.d.ts for full TypeScript types, with type-coverage enforcing 100% strict coverage. It targets Node.js 14.14+/16.0+ as well as Deno and browsers via esm.sh. Runtime dependencies are limited to three single-purpose wooorm packages — is-alphabetical, is-alphanumerical, and is-decimal. Build and quality tooling includes tsc for type-checking and declaration generation, xo for linting, prettier for formatting, remark-cli with remark-preset-wooorm for linting the README itself, c8 for coverage, and GitHub Actions running against both the current Node.js LTS and latest releases.

Code Quality Tests live in test/parse.js and test/stringify.js using node:test and node:assert/strict, plus a data-driven test/fixtures.js that iterates real-world tag fixtures under test/fixtures. c8 enforces 100% coverage (c8 --100) as part of the test script. Error handling is explicit: parse() throws synchronously on null/undefined input, and internal failures route through a single fail() closure that either invokes a caller-supplied warning callback or returns an empty/partial schema — nothing is silently swallowed. Naming is terse but consistent with the author’s other packages, and the whole codebase is typed via JSDoc with strict type-coverage enforcement in CI.

API Design The public surface is exactly two named exports, parse and stringify, built around a Schema object designed to round-trip losslessly between them. Defaults favor safety (forgiving: false, normalize: true) while still exposing an escape hatch — the warning callback — for callers who want detail on why a tag failed rather than a bare success/failure signal. There’s no configuration object to construct and no class to instantiate; getting started is a single import and a single function call, consistent with the author’s broader pattern of small, single-purpose unified-ecosystem packages.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search