@streamdown/cjk

Improved handling of Chinese, Japanese, and Korean text.

The @streamdown/cjk plugin improves handling of CJK (Chinese, Japanese, Korean) text with proper emphasis formatting and autolink handling. This is particularly important for AI-generated content, where language models naturally place emphasis markers around phrases that include or end with punctuation.

  • Correct emphasis formatting near ideographic punctuation (bold, italic, strikethrough)
  • Splits autolinks at CJK punctuation boundaries to prevent URLs from swallowing trailing punctuation
  • Uses remark-cjk-friendly and remark-cjk-friendly-gfm-strikethrough for proper parsing

Install

npm install @streamdown/cjk

Usage

chat.tsx
import { cjk } from '@streamdown/cjk';

<Streamdown plugins={{ cjk }}>
  {markdown}
</Streamdown>

For advanced configuration, use createCjkPlugin:

app/page.tsx
import { Streamdown } from "streamdown";
import { createCjkPlugin } from "@streamdown/cjk";

const cjk = createCjkPlugin();

export default function Page() {
  return (
    <Streamdown plugins={{ cjk }}>
      {markdown}
    </Streamdown>
  );
}

The Problem

The CommonMark/GFM specification has a limitation where emphasis markers (** or *) adjacent to ideographic punctuation marks occasionally fail to be recognized. This causes formatting to break in CJK text:

**この文は太字になりません(This won't be bolded)。**この文のせいで(It is due to this sentence)。

Without CJK-friendly parsing, the text above would render as plain text instead of bold because the closing ** appears next to the Japanese period.

Supported Features

Bold Text with Punctuation

Works correctly with all ideographic punctuation marks:

**日本語の文章(括弧付き)。**この文が後に続いても大丈夫です。
**中文文本(带括号)。**这句子继续也没问题。
**한국어 구문(괄호 포함)**을 강조.

Japanese: 日本語の文章(括弧付き)。この文が後に続いても大丈夫です。

Chinese: 中文文本(带括号)。这句子继续也没问题。

Korean: 한국어 구문(괄호 포함)을 강조.

Italic Text with Punctuation

*これは斜体のテキストです(括弧付き)。*この文が後に続いても大丈夫です。
*这是斜体文字(带括号)。*这句子继续也没问题。
*이 텍스트(괄호 포함)*는 기울임꼴입니다.

Japanese: これは斜体のテキストです(括弧付き)。この文が後に続いても大丈夫です。

Chinese: 这是斜体文字(带括号)。这句子继续也没问题。

Korean: 이 텍스트(괄호 포함)는 기울임꼴입니다.

Strikethrough with Punctuation

Streamdown includes remark-cjk-friendly-gfm-strikethrough for proper strikethrough support:

~~削除されたテキスト(括弧付き)。~~この文は正しいです。
~~删除的文字(带括号)。~~这个句子是正确的。
~~이 텍스트(괄호 포함)~~를 삭제합니다.

Japanese: 削除されたテキスト(括弧付き)。この文は正しいです。

Chinese: 删除的文字(带括号)。这个句子是正确的。

Korean: 이 텍스트(괄호 포함)를 삭제합니다。

Mixed Content

CJK and English text work seamlessly together:

**重要提示(Important Notice):**请注意。

Result: 重要提示(Important Notice):请注意。

Supported Punctuation

The plugin handles all common ideographic punctuation marks:

  • Parentheses: ()
  • Brackets: 【】「」〈〉
  • Periods: 。.
  • Commas: ,、
  • Questions:
  • Exclamations:
  • Colons:

Why This Matters for AI

Language models generate markdown naturally, often placing emphasis markers around phrases that include punctuation. Without CJK-friendly parsing, AI-generated content in Chinese, Japanese, or Korean would have broken formatting.

❌ Without CJK support:
  • The model writes: **この用語(読み方など)**について説明します。- The user sees: **この用語(読み方など)**について説明します。 (not bold!)
✅ With CJK support:
  • The model writes: **この用語(読み方など)**について説明します。- The user sees: この用語(読み方など)について説明します。 (properly bolded!)

The CJK plugin also prevents autolinks from swallowing trailing CJK punctuation. When a URL ends with CJK punctuation characters, the plugin splits the link so the punctuation appears as regular text.

Example:

Check out https://example.com。这是一个链接。

Without CJK support, the trailing would be included in the URL. With the plugin, the link ends at https://example.com and the period is rendered as text.

Supported boundary characters:

。.,、?!:;()【】「」『』〈〉《》

Plugin API

The CJK plugin provides remark plugins in a specific order for proper integration:

interface CjkPlugin {
  // Plugins that run BEFORE remarkGfm (e.g., remark-cjk-friendly)
  remarkPluginsBefore: Pluggable[];

  // Plugins that run AFTER remarkGfm (e.g., autolink boundary, strikethrough)
  remarkPluginsAfter: Pluggable[];

  // @deprecated - Use remarkPluginsBefore and remarkPluginsAfter instead
  remarkPlugins: Pluggable[];
}

Streamdown automatically handles the plugin ordering. If integrating manually, ensure:

  1. remarkPluginsBefore runs before remarkGfm (modifies emphasis handling)
  2. remarkPluginsAfter runs after remarkGfm (enhances autolinks and strikethrough)