The Intl API exposes the ICU locale data inside your browser. See which Intl calls leak a fingerprint, why they date your build, and why they resist spoofing.
Ask most people which part of JavaScript's Intl API is used for fingerprinting and they will say the timezone. That is the famous one, and it is covered in depth in Timezone and Locale Leaks. But Intl is not one signal. It is a window onto the locale database your browser shipped with, and that database is large, versioned, and different from one engine, platform and build to the next. The questions you can ask it carry far more information than a single timezone string.
Key Takeaways
Intlexposes the ICU-style locale data compiled into your browser or operating system, so its answers depend on engine, OS and build version, not only on your language setting.- Three locale values can disagree:
navigator.languages(advertised), theAccept-Languageheader (sent), andIntl.DateTimeFormat().resolvedOptions().locale(negotiated by the runtime). The disagreement is itself a signal. Intl.supportedValuesOf()returns long lists of calendars, collations, currencies, numbering systems, time zones and units. The whole list is the fingerprint, and it also dates your build.- Formatting output from
Intl.NumberFormat,Intl.DateTimeFormatandIntl.Collatorcan differ across data versions, and on browsers that honour OS regional settings the defaults can reveal your region even when your UI language is English. - Because the data lives inside the binary, it is hard to fake consistently. Tor Browser and Firefox's
privacy.resistFingerprintingcan normalise it toen-US, at a usability cost.
Timezone: one paragraph, then moving on
Yes, Intl.DateTimeFormat().resolvedOptions().timeZone is the best-known Intl leak, and a mismatch against your IP country is a classic VPN tell. We will not repeat that here; Timezone and Locale Leaks owns the topic. Everything below is the rest of the surface, which is much less discussed.
Three locale values, three sources
A browser holds several notions of "your language", and they come from different places:
navigator.languagesis the ordered list the browser advertises to scripts.Accept-Languageis what the browser actually sends on every request.resolvedOptions().locale, available onIntl.DateTimeFormatand its siblings, is what the runtime negotiated after matching your request against the locale data it really has.
On an untouched browser these line up in predictable ways. If a tool edits the first two and forgets the third, the page sees a claimed identity that the runtime itself does not confirm. This is the same logic as fingerprint consistency and User-Agent spoofing detection: no single value is damning, the contradiction is.
The highest-entropy item: Intl.supportedValuesOf()
Intl.supportedValuesOf(key) takes one of calendar, collation, currency, numberingSystem, timeZone or unit and returns the sorted list of values the runtime supports for it. Those lists are long, and they track the version of the locale data bundled with the build.
The important point is that the list, not any single entry, is the fingerprint. A newly added currency, a renamed or newly recognised time zone, or an additional unit all change the list. Since locale data versions move with browser and operating system releases, the list also acts as a build date stamp: it can place you in a window of releases without ever reading the User-Agent string. Hashing six lists gives a compact identifier that is stable for as long as your browser's data is, and shared by everyone who runs the same data.
Formatting output as a probe
The other approach is to stop listing and start formatting. Give the runtime a fixed input and compare what comes back:
Intl.NumberFormatdecides grouping separators, decimal marks, currency symbol placement and compact notation.Intl.DateTimeFormat.formatToParts()returns the date as labelled parts, so the order of parts and the literal strings between them are visible.Intl.Collatordecides how accented, mixed-case and CJK strings sort.
Different data versions and different platform data can give observably different output for the same input. We deliberately do not list exact strings for particular versions here, because they change and we have not verified them; the point is the class of difference. A tracker does not need to know what the right answer is. It only needs a stable hash of many answers.
Defaults tied to the OS region, not the language
Depending on the browser and platform, some Intl defaults follow the operating system's regional settings rather than the language you chose in the browser. Safari on Apple devices is the clearest case; Chrome and Firefox mostly derive their defaults from the browser's own language setting instead, which is itself a difference between engines. The values that can be affected include:
- the default calendar (Gregorian, Buddhist, Japanese imperial and so on);
- the default numbering system (Latin digits, Arabic-Indic, Devanagari);
- the hour cycle (12-hour or 24-hour) and the first day of the week.
A person can read English pages in a browser whose UI language is English while their OS is set to a Thai or Saudi region. Where the browser honours that setting, navigator.language says English while resolvedOptions() quietly reveals the region. That makes it a distinct signal from the language header, and a useful one for estimating where a device was set up.
Why it is hard to spoof consistently
An anti-detect browser can rewrite the User-Agent, navigator.languages and the timezone string, because those are values on JavaScript objects. The locale data itself is baked into the binary or the OS. To stay coherent, a spoofing layer would have to intercept every Intl constructor, every supportedValuesOf key, every formatToParts call, and also toLocaleString on numbers, dates and strings, and return answers that agree with the claimed platform and version. Miss one and the claimed identity contradicts the runtime. See anti-detect browsers versus real browsers for how these gaps get found in practice.
We describe the signals here so you can understand and test them, not as a recipe for faking them.
Mitigation, and its cost
Tor Browser, and Firefox with privacy.resistFingerprinting enabled, address this surface by normalising it: the timezone is reported as UTC, and when you accept the offer to request English versions of pages, the locale is reported as en-US to every site. Everyone in that crowd looks the same, which is the whole point.
The trade-off is real: dates, numbers and sort order can then be formatted for a locale that is not yours, and a site that trusts the reported value will show a user in Germany or Japan the wrong conventions. Normalisation buys anonymity with convenience, which is why most browsers do not do it by default.
How big is the signal?
Honestly sized, Intl is a medium-entropy contributor, not a canvas-grade one. A very common locale such as en-US on a current Chrome shares its Intl profile with a huge number of people. The signal gets sharper for unusual locales, mixed OS and browser regions, and old or custom builds, and it is most valuable as a cross-check against other signals. For how to think about this, see entropy and anonymity sets. You can see what your own browser reports with the fingerprint check, which runs entirely in your browser and sends nothing to a server.


