anylocale
v1.0.1
Published
Small locale-info reader — when you need to know text direction, first day of week, weekend, calendars and time zones for any locale, straight from native Intl.
Maintainers
Readme
One export. Real CLDR answers. Any locale. ~1kb gzip. Zero dependencies.
Everyone hardcodes this and everyone gets it wrong. Your runtime already ships the correct table for 200+ locales — anylocale is the thin reader. No language lists, no data files, no config.
import { anylocale } from "anylocale";
anylocale("ar-EG").direction; // "rtl"
anylocale("en-GB").weekStart; // 1 — Monday
anylocale("en-US").weekStart; // 7 — Sunday, same language
anylocale("fa-IR").weekend; // [5] — Friday only, not a pair
anylocale("ar-EG").timeZones; // ["Africa/Cairo"]install
npm install anylocaleusage
anylocale(tag);
anylocale([tag, fallback]);tag is a BCP 47 locale tag, or an array used as a fallback chain — the first
tag the runtime actually has data for wins.
Fields are computed on access, so reading direction never asks the runtime for
calendars or time zones. The record still spreads and serialises like a plain
object.
const { direction, weekStart } = anylocale(navigator.language);
JSON.stringify(anylocale("en-US"));
// {"tag":"en-US","direction":"ltr","weekStart":7,…}anylocale.supported reports whether the runtime exposes Intl Locale Info at
all — see compatibility.
recipes
Copy, paste, move on.
// Set document direction without a hand-kept RTL language list
document.documentElement.dir = anylocale(userLocale).direction;
// …or in React
<html lang={locale} dir={anylocale(locale).direction}>
// Order the columns of a date picker
const start = anylocale(locale).weekStart; // 1–7, ISO
const days = Array.from({ length: 7 }, (_, i) => ((start - 1 + i) % 7) + 1);
// Highlight weekend cells — not always Saturday and Sunday
const weekend = new Set(anylocale(locale).weekend);
const isWeekend = (isoDay: number) => weekend.has(isoDay);
// 12- or 24-hour clock, per the locale rather than per the language
const use12h = anylocale(locale).hourCycles[0] === "h12";
// Offer the calendar the region actually uses
anylocale("fa-IR").calendars[0]; // "persian"
anylocale("th-TH").calendars[0]; // "buddhist"
// Suggest a default time zone from the user's locale
anylocale("ar-EG").timeZones[0]; // "Africa/Cairo"
// Degrade gracefully on runtimes without Intl Locale Info
const dir = anylocale.supported ? anylocale(locale).direction : "ltr";anylocale is pure and synchronous — no clock, no state — so server and client render identically.
fields
| Field | Type | What it is |
| --- | --- | --- |
| tag | string | the canonical tag that was resolved — "en-us" → "en-US" |
| direction | "ltr" \| "rtl" | text direction of the locale's script |
| weekStart | 1–7 | first day of the week, ISO numbering |
| weekend | number[] | days counted as the weekend, ISO numbering |
| minimalDays | number | days of a week that must fall in a year for it to be that year's first week |
| calendars | string[] | usable calendars, preferred first |
| timeZones | string[] | IANA zones for the region; empty for language-only tags |
| hourCycles | string[] | "h12", "h23", … preferred first |
| numberingSystems | string[] | "latn", "arab", … preferred first |
weekStart and weekend are ISO: 1 is Monday, 7 is Sunday. JavaScript's
Date.prototype.getDay() is not — it returns 0 for Sunday. Convert with
iso % 7.
→ Every field, with the surprising cases
what people get wrong
Straight from the data, not from opinion:
| | en-US | en-GB | ar-EG | he-IL | fa-IR |
| --- | --- | --- | --- | --- | --- |
| direction | ltr | ltr | rtl | rtl | rtl |
| week starts | Sun | Mon | Sat | Sun | Sat |
| weekend | Sat, Sun | Sat, Sun | Fri, Sat | Fri, Sat | Fri |
| clock | h12 | h23 | h12 | h23 | h23 |
| digits | latn | latn | arab | latn | arabext |
- Same language, different week.
en-USstarts Sunday,en-GBMonday. A table keyed on language is wrong for half the English-speaking world. - The weekend is not always a pair.
fa-IRhas one day. - RTL does not imply Arabic digits.
he-ILis right-to-left and uses Latin numerals. - Nor does language decide the clock.
en-USis 12-hour,en-GBis 24.
locales
Any valid BCP 47 tag. A fallback chain resolves to the first tag the runtime has
data for, not merely the first that parses — "xx-Nope" is well-formed BCP
47 and would otherwise win.
anylocale("pt-BR").tag; // "pt-BR"
anylocale(["xx-Nope", "de-DE"]).tag; // "de-DE"
anylocale("en-us").tag; // "en-US" — canonicalisedIf no tag in the chain has data, the first well-formed one is used and the runtime answers with its own defaults, rather than throwing.
vs the alternatives
| | anylocale | rtl-detect | hand-kept tables | | --- | :---: | :---: | :---: | | gzip | ~1kb | ~2kb | 0 (yours) | | locale data bundled | no | yes | yes | | covers | direction, week, calendars, zones, clock, digits | direction | whatever you wrote | | stays current | with the runtime's ICU | with releases | never | | dependencies | 0 | 0 | 0 |
anylocale answers how a locale behaves. For what a code is called —
"US" → "United States" — that is
anyaround's job.
stability
anylocale follows semver. The public API is a single
export — anylocale, with anylocale.supported on it — plus AnylocaleInfo
and the exported types. It only changes shape in a major release.
Values come from the runtime's CLDR data and can shift between ICU versions, so test behaviour rather than exact arrays.
compatibility
Intl Locale Info reached Stage 4 (ES2026). It was standardised twice: first
as properties (locale.weekInfo), then as methods (locale.getWeekInfo()).
Engines are split — Node 22 ships only the properties, and nothing else in the
family has to deal with two shapes of the same API. anylocale reads whichever it
finds, so you never have to.
Because support is uneven and moving, feature-detect rather than trust a version table:
const dir = anylocale.supported ? anylocale(locale).direction : "ltr";anylocale.supported is false on engines with neither shape, and every call
throws there. The package itself runs anywhere Node 18+ runs; the data is what
may be missing.
CI runs the full suite on Node 20, 22 and 24, skipping the data-dependent tests wherever the API is absent.
the any family
anylocale is part of any family — tiny, zero-dependency wrappers over native
Intl, one API per package.
| | | |
| --- | --- | --- |
| anywhen | dates & relative time | Intl.DateTimeFormat |
| anyamount | numbers, currency, units | Intl.NumberFormat |
| anymany | lists | Intl.ListFormat |
| anyaround | names & flags | Intl.DisplayNames |
| anylong | durations | Intl.DurationFormat |
| anyplural | plurals | Intl.PluralRules |
| anyword | words & graphemes | Intl.Segmenter |
| anylocale | locale behaviour | Intl.Locale info |
Want all of them? anyfamily is one
install for the lot, and anyfamily-react
wraps each as a hook with a shared locale provider.
npm install anyfamilyMIT © kirilinsky
