npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

typesbcp47

v1.24.4

Published

TypeScript library for parsing BCP47 language tags

Readme

TypesBCP47

npm version npm license TypeScript

TypeScript/Node.js library that parses the IANA Language Subtag Registry and exposes it as typed objects, to validate, normalize and describe BCP 47 language tags.

Installation

npm install typesbcp47

How it works

The package bundles a copy of language-subtag-registry.txt (the raw IANA registry file) and parses it at runtime with RegistryParser — there are no network calls. The registry's File-Date can be read with RegistryParser.getRegistryDate().

Only the language, region, script and variant record types are loaded into memory. extlang, grandfathered and redundant records (over 350 entries combined) are parsed but not indexed, so tags that rely on them (e.g. grandfathered tags like art-lojban, or extlang subtags like ar-afb) are not recognized by isValidLanguageTag/normalizeCode/getTagDescription.

A second, independent data source is used for locale-aware language names: extendedLanguageList_{locale}.xml (~866 entries, includes language+region combinations such as af-NA) and languageList_{locale}.xml (~336 "common" entries). These are curated/translated name lists, not derived from the IANA registry, and are currently shipped only for en, es and fr — requesting any other locale throws. LanguageUtils.isBiDi() also reads its data from these files, not from the registry.

Runtime notes

  • Node.js only. Resources are read from disk relative to the compiled module's location (node:fs/node:path/node:url); the package is not meant to be bundled directly for the browser.
  • ESM onlypackage.json sets "type": "module" and the exports map has no require condition.
  • Depends on typesxml (same author) for XML parsing of the resource files.
  • LanguageUtils keeps static, process-wide caches (registryParser singleton, languagesCache per locale, bidiCodes). Data is parsed once, lazily, on first use, and reused for the life of the process — it is not reloaded or invalidated afterwards.

Exported classes

| Class | Purpose | | ----- | ------- | | LanguageUtils | Static facade — the main entry point, see table below. | | RegistryParser | Parses language-subtag-registry.txt into languages/regions/scripts/variants maps and private-use ranges. Exposes getRegistryDate(). LanguageUtils creates one lazily on first use; you can also instantiate it directly for lower-level access to the parsed registry. | | RegistryEntry | One raw %%-delimited record from the registry file, exposed as a Type/Subtag/Description/... key-value map via get(type). | | Language | code + description, plus getSuppressedScript(), isCJK(), isBiDi() (the last two delegate to LanguageUtils). | | Region, Script, Variant | code + description wrappers (Variant also carries getPrefix(), the language it's registered under). |

LanguageUtils API

| Method | Description | | ------ | ----------- | | isValidLanguageTag(tag: string, caseSensitive?: boolean): boolean | Validates a tag against the registry. x-... and ...-x-... private-use tags are always accepted without a registry lookup. If caseSensitive is true, the tag must already be in its canonical case (see normalizeCode). | | normalizeCode(code: string): string \| undefined | Returns the canonical form of a tag: language lowercase, region uppercase (or the registry's UN M.49 numeric code as-is), script title-case, variant lowercase. Returns undefined if the tag doesn't resolve against the registry. | | getTagDescription(tag: string): string \| undefined | Builds a human-readable description by composing subtag descriptions from the registry, e.g. en-GBEnglish (United Kingdom). Handles at most 3 subtags (language + one of script/region/variant + one more of region/variant); does not compose descriptions for tags with 4+ subtags or more than one variant. Returns undefined for an unknown tag, and also when the script subtag matches the language's Suppress-Script (e.g. en-Latn, since it's redundant). | | getLanguage(code: string, locale: string): Language \| undefined | Looks up a Language by code, normalizing it first; description is in locale. | | getLanguages(locale: string): Array<Language> | All ~866 entries from extendedLanguageList_{locale}.xml, sorted by description using locale-aware collation. | | getCommonLanguages(locale: string): Array<Language> | The smaller ~336-entry curated subset from languageList_{locale}.xml. | | isCJK(code: string): boolean | Pure prefix check on the code — true if it starts with zh, ja, ko, vi, ain or aib. Not a registry lookup. | | isBiDi(code: string): boolean | true if code is one of the languages flagged bidi="true" in the locale XML (currently: ar, ckb, fa, he, ks, lrc, mzn, ps, sd, ug, ur, yi). Lazily loads the en list on first call if no locale data has been loaded yet. |

Note: locale parameters accept en, es or fr only.

Example

import { LanguageUtils } from 'typesbcp47';

const tag = 'en-GB';

if (LanguageUtils.isValidLanguageTag(tag)) {
  console.log(LanguageUtils.getTagDescription(tag)); // "English (United Kingdom)"
  console.log(LanguageUtils.normalizeCode('EN-gb'));  // "en-GB"
  console.log(LanguageUtils.isBiDi('ar'));            // true
  console.log(LanguageUtils.isCJK('ja'));              // true
}

const language = LanguageUtils.getLanguage('fr-CA', 'es');
if (language) {
  console.log(language.getDescription()); // Spanish-language description of "fr-CA"
}