mrkeyoor.com_
Wed 23 Sept 00:37 UTC
npmUtilsupdated 22 Sept 2026

lookup-closest-locale review

lookup-closest-locale 6.2.0 searches an object for an exact locale key, then removes trailing subtags until one matches. pt-BR can fall back to pt, and an ordered array tries each preference in turn. Our install was a 0.7 KB minified browser bundle with bundled declarations and no dependencies. The source assumes normalized, case-matching tags. It does not parse Accept-Language weights, canonicalize input, perform best-fit matching, load messages, or choose a default.

Verdict

lookup-closest-locale 6.2.0 installed one package in 0.6 seconds and bundled to 0.5 KB gzipped in our sandbox, with 0 audit findings. Keep it for tested parent-tag fallback; new HTTP negotiation code should use a maintained matcher that handles canonicalization and weights.

We installed it

Lab card: what happened when we installed lookup-closest-localeScreenshot of lookup-closest-locale documentation
Install✓ · 0.6s1 package on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browser0.5 KBgzipped (0.7 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does lookup-closest-locale install cleanly?

Yes. In a fresh container with an empty cache, npm install lookup-closest-locale finished in 0.6s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does lookup-closest-locale add to a browser bundle?

0.5 KB gzipped (0.7 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does lookup-closest-locale work with both ESM and CommonJS?

Yes. Both import 'lookup-closest-locale' and require('lookup-closest-locale') worked in Node 22 in our run. The package is published as CommonJS.

Does lookup-closest-locale include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

lookup-closest-locale or @formatjs/intl-localematcher: which should you use?

@formatjs/intl-localematcher: Use it for canonical locale lists with lookup and best-fit matching. lookup-closest-locale 6.2.0 installed one package in 0.6 seconds and bundled to 0.5 KB gzipped in our sandbox, with 0 audit findings.

When should you not use lookup-closest-locale?

The input is a raw Accept-Language header; q weights, wildcards, and header parsing are outside the function

API stability4/5The 6.2.0 implementation is a single CommonJS function whose observable behavior has been unchanged since its 2018 publication. It accepts a string, array, or absent locale and returns a matching key or undefined. That frozen surface is predictable, but truthy-value lookup, inherited properties, and case-sensitive matching are quirks that cannot be tightened without changing existing results.
Docs2/5The package description and source comment state the parent-tag algorithm, RFC 4647 reference, normalization assumption, and case-sensitive behavior. The published code is short enough to audit directly. There is no package README at the expected repository path, and users must inspect source to discover falsy-value lookup, undefined fallback, and the lack of Accept-Language parsing.
Maintenance2/5npm shows that 6.2.0 was published on September 12, 2018. The containing repository was pushed on February 15, 2026 and is not archived, but its main README says the maintainers are seeking someone to take over. GitHub reported 28 open issues and PRs for the whole monorepo, not specifically this 20-line helper.
Ecosystem4/5The npm endpoint counted 3,888,744 downloads for the week ending August 24, 2026, while the format-message repository had 206 stars. Much of that use is likely indirect through internationalization tooling. The function accepts plain strings and objects with no framework coupling, but it offers no integration layer for HTTP headers, Intl.Locale, catalog loading, or translation formatting.

Use it if

  • Inputs are already normalized BCP 47 tags and exact-then-parent fallback is the desired policy
  • Available catalogs are stored in a plain lookup object with truthy values
  • A short ordered preference array is prepared before the call
  • Existing format-message code depends on this exact 6.2.0 matching behavior
Skip it if

Setup reality

Our clean Node 22 sandbox installed lookup-closest-locale 6.2.0 in 0.6 seconds. One package used 1 MB on disk; the archive is 16 KB unpacked and declares 0 direct dependencies plus 0 peers. npm audit reported 0 known vulnerabilities. It is CommonJS without an exports map, and both require() and ESM import worked. TypeScript declarations are bundled. A full esbuild import measured 0.7 KB minified and 0.5 KB gzipped.

There are no credentials, config files, or native modules. Input preparation is the job. The implementation compares tags case-sensitively and assumes normalization, so canonicalize browser, profile, or URL values before lookup. An array is taken in its existing order; passing an Accept-Language header as one string makes the complete header one candidate and ignores q values. No match returns undefined, which every call site must turn into a default or an error.

The second argument behaves less like a Set than its name suggests. Source code tests available[candidate] for truthiness, not ownership. An intentionally empty catalog therefore disappears, and inherited properties on a normal object can be visible. Build a null-prototype lookup with truthy catalog records, or wrap the package behind a stricter adapter. Version 6.2.0 dates to 2018, so pin these edge cases in tests if replacing it would change production locale selection.

Patterns

Match an exact locale key match-parent-locale

const lookupClosestLocale = require('lookup-closest-locale');

const catalogs = {
  en: { greeting: 'Hello' },
  fr: { greeting: 'Bonjour' },
};

const locale = lookupClosestLocale('fr-CA', catalogs);
console.log(locale); // 'fr'

An exact truthy key returns immediately before any subtag removal.

Fall back to a parent language prefer-locale-list

const lookupClosestLocale = require('lookup-closest-locale');

const available = { de: true, en: true };
const match = lookupClosestLocale(['fr-CA', 'de-AT', 'en-US'], available);
console.log(match); // 'de'

The lookup removes the rightmost dash segment until a truthy key such as pt is found.

Try ordered locale preferences choose-catalog

const lookupClosestLocale = require('lookup-closest-locale');

const catalogs = {
  en: require('./locales/en.json'),
  'pt-BR': require('./locales/pt-BR.json'),
  pt: require('./locales/pt.json'),
};
const key = lookupClosestLocale('pt-BR', catalogs);
const messages = key ? catalogs[key] : catalogs.en;

Arrays are checked from first to last; the function does not calculate preference weights.

Return an application default provide-default-locale

const lookupClosestLocale = require('lookup-closest-locale');

const available = { en: true, es: true };
const selected = lookupClosestLocale(userLocales, available) ?? 'en';

No match returns undefined, so the caller must select the fallback explicitly.

Canonicalize user input first canonicalize-tags

const lookupClosestLocale = require('lookup-closest-locale');

function canonicalize(values) {
  return Intl.getCanonicalLocales(values);
}

const available = { 'en-US': true, 'fr-CA': true };
const match = lookupClosestLocale(canonicalize(['EN-us']), available);

Version 6.2.0 assumes normalized tags and compares them with case sensitivity.

Build a null-prototype availability map build-availability-index

const lookupClosestLocale = require('lookup-closest-locale');

const localeKeys = ['en', 'fr', 'zh-Hant'];
const available = Object.fromEntries(localeKeys.map((key) => [key, true]));
const match = lookupClosestLocale('zh-Hant-TW', available);

A null prototype prevents inherited names from appearing as available locale properties.

Preserve empty catalogs safely avoid-prototype-matches

const lookupClosestLocale = require('lookup-closest-locale');

const available = Object.assign(Object.create(null), {
  en: true,
  fr: true,
});
const match = lookupClosestLocale(requested, available);

Falsy values are treated as absent; wrap them in a truthy record when an empty catalog is valid.

Parse an HTTP language header separately preserve-empty-catalog

const lookupClosestLocale = require('lookup-closest-locale');

const catalogs = { en: '', fr: 'messages-fr' };
const availability = { en: true, fr: true };
const key = lookupClosestLocale('en-US', availability);
const catalog = key === undefined ? undefined : catalogs[key];

The function does not understand q values or wildcards, so use a header parser before it.

Test case-sensitive behavior convert-map-input

const lookupClosestLocale = require('lookup-closest-locale');

const catalogs = new Map([
  ['en', { hello: 'Hello' }],
  ['ja', { hello: 'こんにちは' }],
]);
const available = Object.fromEntries([...catalogs.keys()].map((key) => [key, true]));
const key = lookupClosestLocale('ja-JP', available);
const catalog = key ? catalogs.get(key) : undefined;

EN-us and en-US are different keys under the documented case-sensitive lookup.

Wrap the frozen dependency parse-accept-language

const parser = require('accept-language-parser');
const lookupClosestLocale = require('lookup-closest-locale');

const requested = parser.parse(req.headers['accept-language'] || '')
  .map(({ code, script, region }) => [code, script, region].filter(Boolean).join('-'));
const locale = lookupClosestLocale(requested, { en: true, fr: true }) ?? 'en';

A local adapter can enforce canonical input and a default while preserving the 0.5 KB gzipped helper underneath.

Alternatives

PackageRegistryPick it when
@formatjs/intl-localematchernpmUse it for canonical locale lists with lookup and best-fit matching.
locale-matchernpmUse it when a small matcher should also normalize locale inputs.
negotiatornpmUse it to parse HTTP Accept-Language and other negotiation headers.
accept-language-parsernpmUse it when weighted header parsing is the primary task.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.