How to Count Chinese, Japanese & Korean Words (2026)
Splitting on spaces gives the wrong count for Chinese and Japanese. Here is how to count CJK words correctly with Intl.Segmenter and character counts.
How to Count Chinese, Japanese & Korean Words Correctly
Paste a paragraph of Chinese into most word counters and you'll get a wildly wrong number — usually 1, occasionally the character count, rarely the truth. To count Chinese, Japanese, and Korean words correctly you cannot split on spaces, because these scripts don't separate words the way English does. This guide covers what actually works: Unicode text segmentation, character counts, and the per-language rules that trip up nearly every naive counter.
TL;DR
- Splitting on spaces fails for Chinese and Japanese — they write words without spaces.
- Use
Intl.Segmenterwithgranularity: "word"; it's Baseline in all browsers since 2024. - For Chinese, character count is the practical unit — that's how translators bill.
- Korean does use spaces, but the unit is the eojeol, not a Western word.
- All of this runs in the browser; your text never leaves your machine.
Why counting words in Chinese, Japanese, and Korean breaks
English lets you cheat. Words are separated by spaces, so text.split(" ") gets you a usable count in one line. That single assumption — a space means a word boundary — is baked into almost every counter, and it collapses the moment you feed it CJK text.
Why split(" ") returns garbage for CJK text
Chinese and Japanese don't put spaces between words. A sentence like 吾輩は猫である is one unbroken run of characters, so split(" ") returns an array of length 1 and your counter proudly reports "1 word" for an entire paragraph. There is no separator to split on. The problem isn't a bug in your code; it's that the rule you're relying on doesn't exist in the writing system.
Character count vs word count — which one you actually want
For CJK text, the more honest question is often "how many characters?" rather than "how many words?" A character count is exact and reproducible: every tool agrees on it. A word count depends on where you decide words begin and end, and in Chinese and Japanese that's a real linguistic judgement, not a lookup. This is why, for many CJK workflows, a good character counter beats a word counter — and why iKit's Word & Character Counter reports both side by side.
The three scripts don't share one rule
"CJK" gets written as one acronym, but the three languages behave differently. Chinese has no inter-word spaces and no inflection. Japanese mixes four scripts (kanji, hiragana, katakana, and Latin) with no spaces. Korean does use spaces — but the thing between the spaces isn't a word in the English sense. One rule can't cover all three, so we'll take them one at a time.
How to count Japanese words correctly in JavaScript
Japanese is the hardest of the three to eyeball, because a single sentence blends kanji and two kana scripts with no delimiters. Fortunately, the browser now ships a proper segmenter.
Using Intl.Segmenter with granularity: "word"
Intl.Segmenter applies the Unicode segmentation algorithm for a given locale. Per the MDN reference, it reached Baseline "newly available" in 2024 once all three major engines shipped it, so you no longer need a third-party dictionary library for basic counting. Set the locale to Japanese and ask for word granularity:
const seg = new Intl.Segmenter("ja-JP", {
granularity: "word",
});
const parts = seg.segment(
"吾輩は猫である。名前はたぬき。",
);
const words = [...parts]
.filter((p) => p.isWordLike)
.map((p) => p.segment);
console.log(words.length); // 8
That sentence segments into eight word-like units: 吾輩 は 猫 で ある 名前 は たぬき. A naive str.length on the same string (minus the two full stops) would have guessed 13.
Filtering punctuation with isWordLike
Each segment carries an isWordLike boolean that separates real words from punctuation, spaces, and symbols. Filtering on it — as in the snippet above — is what keeps the 。 full stops out of your total. Skip that filter and your counts drift upward every time the text contains punctuation, which is always.
Why str.length lies about character count too
Even a plain character count has a trap. str.length returns the number of UTF-16 code units, not user-perceived characters. Common CJK characters live in the Basic Multilingual Plane and count as 1, so the number often looks correct — but rare Han characters from the Unicode extension blocks, plus any emoji, are astral-plane code points that count as 2. For an exact character count, use a grapheme segmenter:
const g = new Intl.Segmenter("ja-JP", {
granularity: "grapheme",
});
const chars = [...g.segment(text)].length;
How to count Chinese characters vs words
Chinese removes one difficulty and adds another: there's no inflection to worry about, but word boundaries are so fluid that the industry mostly stopped counting words at all.
Why Chinese is billed per character, not per word
Chinese characters combine into different "words" depending on context, and the same character appears in many combinations, so an automatic word count is unreliable by nature. The translation industry sidesteps this by pricing per character. As a rough planning ratio, 1,000 English words expands to roughly 1,500–1,700 Chinese characters, which is why buyers convert between the two with a multiplier near 1.5. Even Microsoft Word leans this way: it labels CJK glyphs as "Asian characters" and, in its Word Count dialog, exposes both "characters (with spaces)" and "characters (no spaces)" — the no-spaces figure being the one used for CJK billing.
Counting Han characters with a Unicode range
If a character count is what you want, match the Han script directly with a Unicode property escape. This ignores kana, Latin letters, and punctuation, counting only Chinese characters:
const text = "你好世界,欢迎光临。";
const han = text.match(/\p{Script=Han}/gu) || [];
console.log(han.length); // 8
The u flag is required for \p{...} to work, and Script=Han covers the CJK Unified Ideographs used across Chinese (and the kanji shared by Japanese). If you're storing or transmitting that text, remember that each of these characters is multiple bytes in UTF-8 — worth keeping in mind if you ever encode CJK as HTML entities, where one glyph becomes a numeric reference like 你.
When you genuinely need word segmentation
Sometimes you really do need words — for search indexing, readability scoring, or linguistic analysis. That's where Intl.Segmenter with zh earns its keep, though be honest about its limits: the Unicode default is a well-defined approximation, not a dictionary-perfect parse. Per UAX #29, the standard's own guidance is that, absent a more sophisticated mechanism, the algorithm "supplies a well-defined default" for where boundaries fall. For accuracy beyond that, you'd reach for a dictionary-based analyzer — but for counting, the default is usually close enough.
How to count Korean words (eojeol vs morpheme)
Korean is the odd one out: it does put spaces between units. That makes it feel familiar, and that familiarity is the trap.
Korean does use spaces — but the unit is the eojeol
The space-delimited chunk in Korean is called an eojeol, and it isn't a word in the English sense. An eojeol bundles a meaning-bearing stem with the particles and endings attached to it — a noun plus its postposition, or a verb plus its tense and honorific markers. So split(" ") gives you a count, but it's a count of eojeols, which tend to pack more grammar than a single English word does.
Eojeol vs morpheme: two valid answers
Because an eojeol is internally complex, Korean has more than one defensible "word" count. Research on word segmentation granularity in Korean describes a spectrum: from the eojeol at the coarse end down to individual morphemes at the fine end, with intermediate levels that split off only functional morphemes like case markers and verb endings. Which number is "right" depends on what you're measuring — billing, readability, or NLP input.
A practical default for Korean
For a general-purpose counter, two sensible options exist:
- Eojeol count — split on whitespace. Fast, matches what a Korean reader sees as spacing units, good enough for length estimates.
- Segmenter count —
Intl.Segmenterwithkoand word granularity, which breaks eojeols into finer word-like pieces closer to morphemes.
Pick one and label it clearly. The mistake isn't choosing eojeol or morpheme; it's reporting a number without saying which you counted.
Building a CJK-aware counter that just works
You don't need three separate code paths. One function, given the right locale, handles all three scripts — and gracefully handles English too.
A locale-aware word counter
function countWords(text, locale = "en") {
const seg = new Intl.Segmenter(locale, {
granularity: "word",
});
let n = 0;
for (const p of seg.segment(text)) {
if (p.isWordLike) n++;
}
return n;
}
Pass "ja-JP", "zh", "ko", or "en" and the segmenter applies the appropriate Unicode rules. You can even detect the dominant script with a quick \p{Script=Han} / \p{Script=Hiragana} test and choose the locale automatically.
A comparison of methods
| Method | Good for | Watch out for |
|---|---|---|
split(" ") |
English words | Returns 1 for Chinese/Japanese |
str.length |
Rough char count | Miscounts astral chars + emoji |
Intl.Segmenter |
Real word count | Locale must be set correctly |
\p{Script=Han} |
Chinese chars | Ignores kana and Latin |
And the unit each language actually wants:
| Language | Spaces between words? | Practical unit |
|---|---|---|
| Chinese | No | Character (Han) |
| Japanese | No | Segmented word or character |
| Korean | Yes (eojeol) | Eojeol or morpheme |
Where privacy comes in
Text you're counting is often text you can't share — an unpublished translation, a contract, a client's marketing copy. Counting it shouldn't require uploading it anywhere. Because Intl.Segmenter runs entirely in the browser, a client-side counter gives you accurate CJK numbers with nothing sent to a server. The same holds when you're comparing two versions of a translation: a browser-based diff tool keeps both drafts on your machine while you check them line by line.
References
- UAX #29: Unicode Text Segmentation — the standard defining default word, grapheme, and sentence boundaries; cited for CJK ideograph handling and the "well-defined default" guidance.
- Locale-sensitive text segmentation in JavaScript with Intl.Segmenter — MDN walkthrough; source for the Japanese 8-word example,
isWordLike, and Baseline 2024 status. - Intl.Segmenter reference — API details for locale, granularity options, and grapheme counting.
- Word segmentation granularity in Korean — academic source for the eojeol-to-morpheme spectrum and Korean spacing units.
Related on iKit
- Live word, character, and reading-time counts without uploads — the counter this article's techniques power, with both word and character totals shown side by side.
- Why one emoji or CJK character counts as two on X — X's weighted counting treats each CJK character as 2, the same astral-plane issue
str.lengthruns into. - Why going over the SMS 160-character limit costs money — CJK text forces UCS-2 encoding, dropping the SMS limit to 70; another place character counting has real consequences.
- How many words per minute people actually read — reading-time math assumes a word count, which is exactly what CJK makes tricky to define.
- SEO title and meta description length in 2026 — another counting task where the unit (pixels, characters) matters more than raw word totals.
Related posts
HEX to RGB: How the Conversion Actually Works (2026)
A HEX to RGB conversion is just base-16 to base-10 on three bytes. Here is the exact math, the shorthand rule, alpha, and the CSS syntax for 2026.
Why Your Diff Shows Whole-Line Changes for One Word (2026)
Change one word and your diff lights up the whole line red and green. Here is why line-based diff does that, and how to switch to word-level.
SMS 160-Character Limit: Why Going Over Costs You Money (2026)
The SMS 160-character limit comes from a 140-byte cap. One emoji or smart quote can drop it to 70 and double your bill — here's exactly why.