iKit
Technical · 9 min read ·

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 (2026)

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.Segmenter with granularity: "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 countIntl.Segmenter with ko and 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

Related on iKit

Related posts