npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dawn-studio/zh-convert

v0.5.0

Published

Chinese script conversion for Dawn Studio apps: Simplified <-> Traditional (phrase-aware, idempotent), lazy-loaded, never-throw. Engines: tongwen-core + tongwen-dict (MIT, default) and OpenCC via opencc-js (opt-in, config-based: s2t/s2tw/s2hk/s2twp/t2s/…)

Readme

@dawn-studio/zh-convert

Chinese script conversion for Dawn Studio apps: Simplified ⇄ Traditional (phrase-aware, idempotent), lazy-loaded, never-throw. Extracted from OpenMusic's battle-tested zh-convert + i18n/detect modules.

Default engine: tongwen-core + tongwen-dict (both MIT) — declared as real dependencies, installed automatically. Since 0.5.0 there is also an opt-in OpenCC engine (config-based, see below). This package adds the house layer:

  • Lazy, memoized dict loads via dynamic import() — the ~72 KB s2t / ~22 KB t2s tables stay out of the initial bundle, fetched at most once.
  • Synchronous handles (s2tConvertLineSync, t2sConvertLineSync) published once warm — no-flash first renders.
  • Idempotent s2t merge — tongwen's s2t phrase table is keyed on Simplified, so direct s2t of an already-Traditional 周杰倫 misses the phrase and per-char maps 杰 → 傑 (→ 周傑倫). The merge folds to Simplified first, then per code point keeps the source spelling wherever t2s changed the char.
  • Never-throw — any import/build/convert failure degrades to identity, never a throw.
  • Japanese guard — the kana/hangul-first classifier means Japanese lines (kana present) are never wrongly Traditionalized.

Install

npm i @dawn-studio/zh-convert

Quick start

import {
  lockScriptSync,   // force one line into a script — the entry point most apps want
  warmScript,       // await dict builds so the first render doesn't flash
  s2tConvertLines,  // batch Simplified → Traditional (position-aligned)
  t2sConvertLines,  // batch Traditional → Simplified
  isChineseLine,    // predicate: safe to convert offline?
  detectLang,       // pure classifier: en | zh-Hant | zh-Hans | ja | ko
  shouldTranslate,  // per-text-unit "should this be translated?" decision
} from '@dawn-studio/zh-convert';

// 1. Warm early (e.g. app boot) when the target is Traditional:
await warmScript('zh-Hant');

// 2. Force any upstream text into the user's script — Chinese only, everything
//    else passes through untouched, never throws, never returns null:
lockScriptSync('周杰伦', 'zh-Hant'); // → '周杰倫'
lockScriptSync('周杰倫', 'zh-Hant'); // → '周杰倫' (idempotent — source spelling kept)
lockScriptSync('こんにちは', 'zh-Hant'); // → 'こんにちは' (Japanese untouched)

Batch conversion (lyrics, search results):

await s2tConvertLines(['头发', '', '简体中文']); // → ['頭髮', '', '簡體中文']
await t2sConvertLines(['止戰之殤']);              // → ['止战之殇']

No-flash sync path for render loops — returns null until warm:

import { warmS2T, s2tConvertLineSync } from '@dawn-studio/zh-convert';
warmS2T(); // fire-and-forget at boot
// ... in render:
const text = s2tConvertLineSync(name) ?? name;

API

| Export | Description | |---|---| | lockScriptSync(text, target, opts?) | Force one line into 'zh-Hant'/'zh-Hans'. Never throws, never null. Use this. | | warmScript(target) | Await the dict build(s) a lock target needs. Never rejects. | | s2tConvertLines(lines, opts?) | Batch s2t, position-aligned, blanks preserved. | | t2sConvertLines(lines, opts?) | Batch t2s, position-aligned. No-op on already-Simplified input. | | s2tConvertLineSync(text) | Sync s2t (idempotent merge). null until warm. Gate on isChineseLine first. | | t2sConvertLineSync(text) | Sync t2s. null until warm. | | warmS2T() / warmT2S() | Fire-and-forget warmers. | | isChineseLine(text) | true only for detected Chinese — kana/hangul lines return false. | | detectLang(text) | Pure classifier → 'en' \| 'zh-Hant' \| 'zh-Hans' \| 'ja' \| 'ko'. Kana/hangul win over Han; ambiguous Han defaults to 'zh-Hant'. | | shouldTranslate(text, target, whitelist) | Per-unit translation decision; Chinese targets always route through the converter (detection is unreliable on the small char-set). | | convertWithConfig(text, config, opts?) | OpenCC path. Async config-based conversion (per-line guard + protection). Never throws — identity on failure. | | convertWithConfigSync(text, config, opts?) | OpenCC path. Sync conversion once warm; null while cold. No guard — gate first. | | warmConfig(config) | OpenCC path. Await a config's lazy build. Never rejects. | | isConfigWarm(config) | OpenCC path. Whether a config's sync handle is ready. | | OPENCC_CONFIGS / openccConfig(name) | OpenCC path. The preset config table / lookup (unknown names fall back to s2t). |

OpenCC engine (0.5.0+) — config-based conversion

Modelled on OpenCC's preset config system — the same config names as upstream OpenCC and the online converter (?config=s2t). Implemented with opencc-js (the pure-JavaScript port of OpenCC; dictionaries derived from OpenCC data, MIT AND Apache-2.0) — not the official opencc npm package, which is a native addon and cannot run in browsers or on the edge.

import { convertWithConfig, convertWithConfigSync, warmConfig, OPENCC_CONFIGS } from '@dawn-studio/zh-convert';

await convertWithConfig('鼠标', 's2twp');        // → '滑鼠' (Taiwan phrases)
await convertWithConfig('軟體', 'tw2sp');        // → '软件' (Mainland phrases)
await warmConfig('s2t');                          // warm the sync handle…
convertWithConfigSync('汉字', 's2t');             // → '漢字' (null while cold)

Or route the existing APIs through OpenCC with ConvertOptions:

await s2tConvertLines(['鼠标'], { engine: 'opencc', config: 's2twp' }); // → ['滑鼠']
lockScriptSync('汉字', 'zh-Hant', { engine: 'opencc' }); // sync handle — warmConfig('s2t') first; cold → identity

The default engine stays tongwen; OpenCC is strictly opt-in. The OpenCC path keeps every house guarantee: lazy per-config loading (dynamic import(), never in the initial bundle), never-throw identity fallback, the same Chinese-only guard, and PROTECTED_WORDS protection (占 never becomes 佔 on OpenCC paths either; 占有→佔有 still works).

Config table (upstream OpenCC names ↔ opencc-js locales)

Rough script-lock equivalents: zh-Hant ≈ s2t, zh-Hans ≈ t2s — the regional configs (s2tw, s2hk, …) additionally normalize regional variants/phrases, which the script lock deliberately does not do.

| config | from → to | meaning | |---|---|---| | s2t | cn → t | Simplified → Traditional (OpenCC standard) | | t2s | t → cn | Traditional (OpenCC standard) → Simplified | | s2tw | cn → tw | Simplified → Traditional (Taiwan standard) | | tw2s | tw → cn | Traditional (Taiwan) → Simplified | | s2hk | cn → hk | Simplified → Traditional (Hong Kong standard) | | hk2s | hk → cn | Traditional (Hong Kong) → Simplified | | s2twp | cn → twp | Simplified → Traditional (Taiwan, with Taiwan phrases: 鼠标→滑鼠, 软件→軟體) | | tw2sp | twp → cn | Traditional (Taiwan) → Simplified (with Mainland phrases: 軟體→软件) | | t2tw | t → tw | Traditional (OpenCC standard) → Traditional (Taiwan) | | tw2t | tw → t | Traditional (Taiwan) → Traditional (OpenCC standard) | | t2hk | t → hk | Traditional (OpenCC standard) → Traditional (Hong Kong) | | hk2t | hk → t | Traditional (Hong Kong) → Traditional (OpenCC standard) | | s2hkp | cn → hkp | Simplified → Traditional (Hong Kong, with Hong Kong phrases) | | hk2sp | hkp → cn | Traditional (Hong Kong) → Simplified (with Mainland phrases; upstream: still developing) |

ConvertOptions — every knob, your call

lockScriptSync, s2tConvertLines and t2sConvertLines all accept an optional opts: ConvertOptions. Nothing is hardcoded that a reasonable app would want to change:

import { lockScriptSync, type ConvertOptions } from '@dawn-studio/zh-convert';

// The "Chinese only" rule is just the default guard — replace it freely:
lockScriptSync('こんにちは世界', 'zh-Hant');                                  // → unchanged (default guard)
lockScriptSync('简体', 'zh-Hant', { guard: () => true });                     // → '簡體' (allow all)
lockScriptSync('简体', 'zh-Hant', { guard: (t) => t !== '简体' });            // → '简体' (skip this line)

await s2tConvertLines(['简体', '头发'], { guard: isChineseLine });            // explicit default

| option | type | default | notes | |---|---|---|---| | guard | (text: string) => boolean | isChineseLine | Rejected lines pass through untouched. The default is the kana/hangul-first classifier — it already does the right thing for mixed-language text. | | protectedWords | readonly string[] | — | Extra words shielded from conversion, added to PROTECTED_WORDS. | | engine | 'tongwen' \| 'opencc' | 'tongwen' | Conversion engine. 'opencc' routes the async batch converters directly; lockScriptSync uses the warm OpenCC sync handle (cold → identity). | | config | OpenCCConfigName | 's2t' / 't2s' per entry point | OpenCC config name; only used when engine: 'opencc'. |

Notes

  • Engines & attribution: the tongwen dictionaries are MIT (tongwen-core/tongwen-dict); the OpenCC dictionaries come from OpenCC data via opencc-js (opencc-js is MIT AND Apache-2.0; OpenCC data is Apache-2.0). This package itself is Apache-2.0.
  • s2t and t2s dicts are built separately — a t2s-only caller never downloads s2t's 148 KB phrase table.
  • Deep-imports tongwen-core/esm/* (not the top-level index): the index eval-time references the DOM global NodeFilter and throws where there is no DOM.
  • Known ceiling of the default guard: a Japanese line with zero kana (pure kanji) detects as Chinese and will be converted. Override guard if your corpus needs stricter screening.

License

Apache-2.0