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.
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
| Install | ✓ · 0.6s | 1 package on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package |
| Browser | 0.5 KB | gzipped (0.7 KB minified), bundled with esbuild |
| Types | ✓ | TypeScript types bundled |
| Known vulns | 0 | 0 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
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
- The input is a raw Accept-Language header; q weights, wildcards, and header parsing are outside the function
- Codes may differ in case or canonical spelling; the source comment says comparison is case-sensitive
- Available entries may contain empty strings, false, 0, or null; the implementation treats a falsy value as missing
- You need likely-subtag or best-fit locale matching instead of repeatedly dropping the final dash segment
- A maintained release stream is required; 6.2.0 was published in 2018 and the monorepo says it is looking for maintainers
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
| Package | Registry | Pick it when |
|---|---|---|
| @formatjs/intl-localematcher | npm | Use it for canonical locale lists with lookup and best-fit matching. |
| locale-matcher | npm | Use it when a small matcher should also normalize locale inputs. |
| negotiator | npm | Use it to parse HTTP Accept-Language and other negotiation headers. |
| accept-language-parser | npm | Use 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.

