[BUG] CJK word boundary navigation (Option+Arrow) splits common Chinese words into single characters

Open 💬 0 comments Opened Jun 11, 2026 by Loukas922

Follow-up to #11099

#11099 reported that Option+Arrow treated entire Chinese text as one unit. That was fixed by adopting Intl.Segmenter with granularity: 'word' (in utils/intl.ts). The fix helped — most common words are now correctly segmented. However, ICU's Chinese dictionary has gaps that cause some common words/phrases to be split into individual characters, making word navigation inconsistent.

Environment

  • Claude Code: 2.1.173
  • Platform: macOS 15.5
  • Terminal: Ghostty
  • Node.js: v24.9.0

Current Behavior

Option+Left/Right uses Intl.Segmenter word boundaries. For some common Chinese words, ICU's dictionary fails to recognize them as words, splitting them into single characters:

| Input | Segmentation | Problem |
|---|---|---|
| 到底怎么回事啊 | 到底 \| 怎么 \| (回) \| (事) \| (啊) | "回事" split |
| 不管怎样都要试试 | 不管 \| 怎样 \| 都要 \| (试) \| (试) | "试试" (reduplication) split |
| 应该要怎么做才对 | 应该 \| (要) \| 怎么 \| (做) \| 才对 | Single-char verbs isolated |

This means the cursor stops at every single character in these cases, which is worse than zsh's behavior (zsh groups all consecutive CJK characters and jumps over them as a block — coarser but predictable).

Expected Behavior

Two possible improvements (not mutually exclusive):

  1. Fallback strategy for CJK: When Intl.Segmenter produces a single-character word-like segment that is surrounded by other CJK characters, merge it with adjacent single-char segments. This would approximate zsh-style "jump by character class" for poorly-segmented regions while preserving good segmentation where ICU gets it right.
  1. Configurable word navigation mode: Allow users to choose between intl-segmenter (current) and character-class (zsh-style: jump by Unicode script/category boundaries) via settings.

Root Cause

utils/intl.ts:47 creates the word segmenter:

wordSegmenter = new Intl.Segmenter(undefined, { granularity: 'word' })

The locale parameter doesn't help — zh-CN and en-US produce identical Chinese segmentation results (ICU uses the same CJK dictionary regardless of locale).

Cursor.prevWord() / Cursor.nextWord() in utils/Cursor.ts iterate over these segments. When ICU's dictionary misses a word, the cursor stops at every character.

Reproduction

const seg = new Intl.Segmenter(undefined, { granularity: 'word' });
const text = '到底怎么回事啊';
console.log([...seg.segment(text)].filter(s => s.isWordLike).map(s => s.segment));
// → [ '到底', '怎么', '回', '事', '啊' ]
// Expected: '回事' should be one word

View original on GitHub ↗