isChinaUser guesses a China-region browser from five client side signals
🇨🇳 Determine current user is a Chinese user.
At a glance
- What is it?
- yArna/isChinaUser is a small TypeScript library that combines language, timezone, emoji rendering, installed fonts and an optional network probe to decide whether the current browser looks like it belongs to a user in the China region. The default call is synchronous and makes no network request, and the README is unusually clear that this is a hint, not a determination.
- Who is it for?
- isChinaUser suits front-end code that needs a cheap hint to branch content or default settings for a China-region audience, and it is especially useful where no server-side geolocation is available. It does not suit a decision that must be accurate, such as pricing, licensing or access control, because every device side signal can be changed by the user.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 114 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Five signals, and no IP lookup in the default path
isChinaUser is a browser side library that decides whether the current browser environment looks more like a Chinese user or a device in the China region, and the design decision that defines it is that it does not depend on a single signal. The README lists five categories of browser characteristic that it combines. Language information comes from navigator.language and navigator.languages. Timezone information comes from Intl.DateTimeFormat().resolvedOptions().timeZone. Device characteristics are probed with a light check based on emoji rendering differences. Font information comes from detecting whether common Chinese fonts are available to the browser. Network information comes from probing the reachability of certain hosts to judge the current network egress, and that one is asynchronous and optional. Five independent signals with a permissive combination rule means the library is much more likely to fire than a single precise check, and the network probe is excluded from the default call so that nothing leaves the browser unless you ask for it. The package is published on npm as is-china-user, and the repository hosts a live demo site at yarna.github.io/isChinaUser.
The default call is a permissive OR over four signals
The main entry point is a synchronous function that combines the language, timezone, emoji device characteristic and Chinese font signals, and returns true if any one of them is positive. That is the whole rule, and it is worth being precise about the consequences. A user in the China region with a Chinese system language, a neutral device font set and a Windows machine that cannot render colour emoji will still be detected, because the language signal alone is enough. The reverse is also true, because a user anywhere in the world who has a Chinese language pack installed will be flagged. The README says this explicitly, that the function leans towards trying to identify rather than performing a strict classification in any legal or identity sense, and it also says that if you want to identify only mainland China users you need the mainland option. The API surface is a set of small functions, one per signal, plus the combined default, and each signal function can be called on its own when you want to weight the signals differently yourself.
Language and timezone checks with mainland and strict switches
The two simplest signals are also the most configurable. The language check looks at navigator.languages and returns true if any entry is Chinese. Two options refine it. With mainland set to true, only language tags that lean towards a mainland simplified environment are accepted, such as zh-CN and zh-Hans. With strict set to true, only the preferred language at navigator.languages[0] is checked, which removes the effect of a Chinese language sitting further down the list of installed languages. The timezone check asks whether the resolved time zone is one of the China related zones, and by default the common mainland, Hong Kong, Macao and Taiwan zones all count as positive signals. With mainland set to true, only the common mainland zones are accepted. There is also a fallback worth knowing about, because it changes the meaning of the strict flag. If the Intl timezone cannot be read, the check falls back to getTimezoneOffset() and uses UTC+8 as the substitute judgement. With strict set to true there is no UTC+8 fallback, and a known time zone is required. So a strict timezone check is really a check that the browser exposes a time zone at all, which is a different property from what the name suggests.
The emoji probe is a differential test, not a flag lookup
The emoji check is the most interesting mechanism in the library, because it is trying to exploit a rendering difference rather than read a setting. The approach uses a plain smiley emoji as a control. The probe only counts as a hit when the device can render ordinary emoji in colour but cannot render a particular regional flag emoji in colour, and that asymmetry is what identifies a mainland device. The README is careful about the false positive risk, noting that the control is there to avoid false hits in environments that lack a colour emoji font, such as some Linux setups, and in browsers where canvas fingerprinting protection is enabled. The return value is three-valued, which is unusual and worth handling properly. True means the device looks like a mainland device, false means no match, and null means it could not be determined, which happens on Windows, in a non-DOM environment, and on devices that do not support colour emoji rendering. A null is not a negative answer, and treating it as one will make you under-detect. The README also notes that this library is a browser side judgement and that isChinaByEmoji should not be called directly in Node.js.
Font detection measures what the browser can actually render
The font check looks for common Chinese fonts such as DengXian and FangSong among the fonts available to the browser, and it can be pointed at a different set of fonts with the fontList option, which the README suggests for teams that want to detect a specific font collection or do finer grained environment probing of their own. The default list is the one to think about first, because the fonts in it are the ones that ship with Chinese Windows editions and with some Chinese Linux distributions, and a user on an English Windows install will not have them. The return value is two-valued, true when a common Chinese font is detected and false when there is no match or when the current environment does not support canvas based detection. That last clause matters, because a false in an environment without canvas support is not evidence of a non-China device, it is evidence that the probe could not run. A well built implementation should treat the font signal as unknown rather than negative when the measurement cannot be taken, and the library's own two-valued API does not make that distinction for you.
The network probe is opt-in and reports three states
The network check is the only asynchronous method, and the README is explicit that it is not used by default because it loads remote probe images. It works by loading two kinds of image at the same time. The mainland-blocked probes, by default the Google search logo favicon and the ChatGPT favicon, are hosts that are typically not reachable from a mainland network, and the control probe, by default the Baidu favicon, is reachable from a mainland network. The interpretation is straightforward. If a blocked probe is reachable the result is false, meaning you are not on a mainland network or you are already on a proxy. If the blocked probe is unreachable but the control is reachable, the result is true. If neither is reachable the result is null, which means you may be offline or blocked by an extension and cannot tell. The call takes a timeout, and passing detail set to true returns the reachability of every probe alongside the result, and you can also pass custom probe lists. What makes this signal different from the others is that it reflects the current network egress, which is much closer to what a server would see about the user, and the README calls it the highest weight signal if you want to know whether a site or API would treat you as a China user. It also produces real image load requests and the result is affected by proxies and VPNs, which is the point of it. The default isChinaUser is synchronous and does not include this detection, so you have to call it yourself.
The package is a tiny ESM build with a node test runner
The packaging is minimal and modern. The package name is is-china-user at version 1.7.0, it is an ES module with type set to module, and it publishes a dist directory only:
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},The scripts show the whole toolchain. The build runs rslib build, the dev script runs the same build in watch mode, the test script builds first and then runs the Node test runner against the test file, and the release script builds and publishes. The only development dependencies are the rslib core, the Node type definitions and TypeScript, and the publish config points at the npmjs.org registry. The repository itself is small, with src/, test/, docs/, an rslib.config.ts, a tsconfig.json and a pnpm-lock.yaml, so the lockfile implies pnpm is the package manager of choice even though the test script invokes npm run. On maintenance, the repository is not archived, the last push was on 2026-07-02, and there are no GitHub releases, so the npm version is the only version signal. The licence field is the one gap, since the repository does not declare a licence, which is worth resolving before you put the package in a shipped product.
Client side guessing versus server side geolocation
The real alternative to this library is deciding on the server. If your application already has the request in front of it, the server can look at the connecting address and resolve it to a country, which is a different kind of signal entirely, since it is derived from the network rather than from the user's settings. The difference in approach is what makes each useful. Server side resolution is accurate for the traffic it sees and invisible to the user, but it depends on the address database you buy or host, it says nothing about the person if they are behind a VPN or a corporate proxy, and it needs a network round trip and, in most jurisdictions, some handling of the personal data involved. The client side signals in isChinaUser are free, instantaneous, need no data from the user and can be combined with your own logic, but they are read from settings the user controls, which the README spells out in a section on how to avoid being detected as a China user. Change the browser language, change the operating system language, move the system time zone to a country neighbouring China, use an English only operating system so the font list has no Chinese fonts, and if the device cannot render certain regional flag emoji, change that in the operating system configuration, which the README notes is the most troublesome of the five. None of that includes the IP address, and the whole discussion assumes a clean address already. That is the honest summary of the library: a reasonable heuristic for presentation defaults, and a poor foundation for anything that must be right.
Editorial conclusion
isChinaUser suits front-end code that needs a cheap hint to branch content or default settings for a China-region audience, and it is especially useful where no server-side geolocation is available. It does not suit a decision that must be accurate, such as pricing, licensing or access control, because every device side signal can be changed by the user. Before using the network probe, confirm your page may issue requests to third-party hosts and that a proxy will not distort the result, and note the last push was on 2026-07-02 with no tagged GitHub releases and a licence the repository does not declare.
Frequently asked questions
How do I install and use isChinaUser?
Install it with npm i is-china-user, then import isChinaUser and call it. A plain call returns true when any of the language, timezone, emoji or font signals is positive, and you can pass { mainland: true } to restrict the result to mainland China users.
Does isChinaUser check my IP address?
Not by default. The default isChinaUser call is synchronous and uses only browser side signals. The network based check is a separate asynchronous method that loads remote probe images, and the README notes that the advice about avoiding detection covers the device side signals and assumes a clean IP address.
What does isChinaByEmoji return when it cannot decide?
It returns null when it cannot be determined, for example on Windows, in a non-DOM environment, or on a device without colour emoji rendering support. The probe uses a plain smiley emoji as a control so it only matches when ordinary emoji render in colour but a regional flag emoji does not.
How does isChinaByNetwork work?
It loads probe images that are typically unreachable from a mainland network alongside a control image that is reachable. If a blocked probe is reachable the result is false, if only the control is reachable the result is true, and if neither is reachable the result is null. Passing detail set to true returns the reachability of each probe, and custom probe lists can be passed.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/yarna-ischinauser)Was this useful?