resolve-accept-language

Resolves the best-matching locale from an HTTP Accept-Language header using the RFC 4647 lookup matching scheme.

Library
npm
v3.2.2
35stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
38/100Needs Attention
Development Activity12
Maintenance48
Community20
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
75/100Good
Architecture80
Code Quality90
Innovation70
Learning Curve60

resolve-accept-language is a small, zero-dependency TypeScript library that implements RFC 4647’s “lookup” matching scheme to pick the single best locale for a request based on its Accept-Language header. Rather than attempting full BCP 47 language-tag support, it deliberately narrows scope to the language-country format (e.g. en-US, fr-CA), arguing that the extra precision of a country code matters more for real-world formatting (dates, numbers, translations) than broader tag coverage would.

The resolver runs an ordered sequence of match stages — exact locale, language-specific locale, language, related locale, language-country, an optional country match, and finally a default-locale fallback — always returning exactly one result. It also special-cases the es-419 (Latin America) UN region code by expanding it into concrete Spanish country locales. TypeScript function overloads preserve literal string types for the locales array so consumers get precise typing without runtime casts, and the package ships dual ESM/CJS builds with full type declarations.

What You Get

  • A single resolveAcceptLanguage() function that returns the best-matching locale as a plain string, or an object with the match and its matchType when returnMatchType is set
  • Seven staged match strategies (locale, language-specific locale, language, related locale, language-country, optional country, default) applied in priority order
  • Built-in handling of the es-419 (Latin America) UN region code, expanded to concrete Spanish country locales like es-MX and es-AR
  • Zero runtime dependencies and a ~2.8kB min+gzip bundle size
  • Dual ESM and CommonJS builds with full TypeScript type declarations, including overloads that preserve literal locale types
  • An optional matchCountry mode for matching a locale sharing the requester’s country before falling back to the default locale

Common Use Cases

  • Selecting which translated locale to serve on the homepage of a multi-locale web app based on the visitor’s browser language settings
  • Implementing locale-based redirects or routing in an SSR framework middleware layer
  • Deciding which currency/date-format locale to apply for a given request without hardcoding a single default
  • Building a language picker that shows a prompt only when the match quality is weak (via returnMatchType)
  • Normalizing inconsistent or partial Accept-Language values (e.g. bare language codes, es-419) into a supported locale list

Under The Hood

Architecture The package is a small, flat module tree organized by responsibility rather than by layer: src/directives.ts parses the raw header string with a single regular expression into an array of IndexedDirective objects (language code, optional country code, optional locale, quality value), sorting them by quality and header position and expanding the es-419 special case into concrete Latin American Spanish locales; src/locales.ts defines Locale and LocaleList classes that normalize case and build O(1) lookup dictionaries for supported languages, countries, and locale identifiers; and src/index.ts contains the core resolveAcceptLanguage() function, which runs the parsed directives against the locale lookup maps through an ordered sequence of match stages, returning as soon as one succeeds. There is no dependency injection, no external state, and no I/O — every function operates purely on its inputs, which keeps the control flow easy to trace end to end. Because the staged-matching loop in index.ts is written directly against the simple language-country identifier assumption baked into Locale, extending the model to support additional BCP 47 subtags (script, variants) would require touching most of the matching logic, not just the data model.

Tech Stack Written in TypeScript with zero runtime dependencies, targeting dual ESM and CommonJS output via exports map plus a custom build script (src/build-scripts/build.ts) that runs tsc and Terser for minification. The devDependency set is oriented entirely around build and quality tooling rather than runtime concerns: Jest and ts-jest for testing, a strict ESLint 10 configuration layering typescript-eslint, eslint-plugin-unicorn, eslint-plugin-prefer-arrow-functions, and eslint-plugin-tsdoc, Prettier for formatting, Husky for git hooks, and release-it for changelog-driven releases. check-node-version enforces the .nvmrc-pinned Node version during builds. The package is distributed purely as an npm library — no server, database, or framework involved.

Code Quality A single, comprehensive Jest test file (tests/resolve-accept-language.test.ts, ~300 lines) exercises every documented match type, edge case, and the invalid-locale error path, backing the README’s claimed 100% coverage. Errors are surfaced as explicit, descriptive Error throws (invalid locale format, default locale not present in the locales array) rather than silently swallowed or defaulted. Naming is consistent and domain-specific (directive, locale, matchType), and the code favors precise typing over convenience: resolveAcceptLanguage uses TypeScript function overloads specifically to preserve literal string types on the locales/defaultLocale parameters and avoid a runtime type cast the project’s own lint rules forbid. A GitHub Actions workflow runs the full build (including typecheck, lint, and test) across three Node version targets on every push and pull request.

What Makes It Unique Most Accept-Language parsers try to support the full BCP 47 tag grammar; this library deliberately narrows scope to the language-country format and leans into an unusually granular seven-stage fallback strategy — including a “related locale” pass and a “language-country” cross-match pass that most competing parsers skip — plus a hardcoded expansion of the es-419 UN region code into concrete Latin American Spanish locales, a specific real-world browser quirk not commonly handled elsewhere.

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