A personal Chinese learning tool built mobile-first, with a focus on the Sino-Vietnamese connection.
- Next.js 16 (App Router) + React 19
- Mantine 9 UI + custom GM* design system
- Bun package manager
- Data: static
database.ts— auto-generated from open data + Claude enrichment
make run # bun dev on :2025
make build # production build
make install # install depsThe app is structured around six modes, accessible from a bottom nav (mobile) or top nav (desktop). Search is the default.
The dictionary. Look up any word across all supported fields.
Word cards show:
- Chinese characters (tap → stroke order animation via Hanzi Writer, auto-resets after 1s)
- Pinyin (tap → pronunciation via Web Speech API, zh-CN)
- Word types & English meaning
- Sino-Vietnamese & Vietnamese meaning
Search input supports:
- Text: Chinese characters, pinyin, English, Sino-Vietnamese & Vietnamese meaning
- Handwriting canvas: draw a character → recognition via stroke-direction index built from hanzi-writer-data, appends to current query
- Voice: use OS keyboard dictation (iOS mic key, macOS Dictation, Android voice input) — no in-app mic needed
Browse and study words. All enrichment is pre-generated and instant — no async calls.
Browse words by:
- HSK level (1–6)
- Topic / thematic group (food, travel, time, etc.)
Word detail — tap any card to expand:
- Meaning explained in Chinese (dictionary-style)
- Example sentences (3 per word)
- Synonyms and antonyms (tap to navigate)
- Words that contain the current word (compounds)
Train listening comprehension. AI-driven.
- AI generates a short conversational paragraph or dialogue (difficulty tied to chosen HSK level)
- Audio is played via TTS (zh-CN)
- User answers multiple-choice comprehension questions
- Answer feedback shows the relevant sentence and vocabulary breakdown
Train pronunciation and tones. AI-driven with deterministic scoring.
- AI generates a short paragraph at the chosen level
- Text is displayed with pinyin shown on demand
- User reads aloud; the app records and scores pronunciation using the Web Speech API or a phoneme-level scoring API
- Each character/word is highlighted as correct, close, or wrong
- Retry individual sentences or the full passage
Annotated reading with full translation layer. Mix of deterministic rendering and AI generation.
- Input: paste any Chinese text, or ask AI to generate a passage at a chosen level and topic
- Output:
- Paragraph rendered with Chinese + inline pinyin
- Tap any word → inline English + Vietnamese gloss
- Full paragraph translation toggle (English / Vietnamese)
- Key vocabulary table at the bottom: Chinese · Pinyin · EN · VI · Sino-VI · HSK level
Chinese writing assistant. AI-driven.
- User types freely in Chinese (or pastes a draft)
- AI returns:
- Grammar corrections with explanations
- Paraphrased alternatives (more natural / more formal)
- Vocabulary upgrade suggestions (e.g. swap a basic word for a richer synonym)
- Tone/register feedback (casual vs. written Chinese)
- Diff view shows original vs. suggested side-by-side
src/database.ts is the word database across HSK 1–6, auto-generated from open data + Claude enrichment. Do not edit it by hand.
Each word:
{
chinese: "学习",
pinyin: "xuéxí",
en: "to study; to learn",
vi: "học tập; học hỏi",
sino_vi: "HỌC TẬP", // null if no Sino-Vietnamese form
types: [Type.V],
hsk: 2,
zh: "学习是指通过阅读、练习等方式获取知识或技能。",
examples: [
{ zh: "我每天学习汉语。", pinyin: "Wǒ měitiān xuéxí Hànyǔ.", en: "I study Chinese every day." },
// ...
],
synonyms: ["学", "研究"], // Chinese only — pinyin/en looked up from WORDS at runtime
antonyms: [],
}All offline pipeline scripts live in scripts/ and are written in TypeScript, run with tsx.
scripts/
constants.ts ← shared: MODEL, VALID_TYPES, BaseWord, UnifiedWord types
database/
1_download_raw.ts ← fetch source txt from GitHub
2_concat_base.ts ← parse txt → 2_base.json
3_enrich_word.ts ← Claude Haiku → 3_enriched.json (resume-safe)
4_generate_database.ts ← emit src/database.ts
data/
1_hsk{1..6}.txt
2_base.json
3_enriched.json
drawing/
1_index_strokes.ts ← build public/hanzi-index/index.json
missing_word/
1_add_missing_word.ts ← enrich + append a single word
data/
1_added.json
| Step | Script | Input | Output | Notes |
|---|---|---|---|---|
| 1 | database/1_download_raw |
glxxyz/hskhsk.com | database/data/1_hsk{1..6}.txt |
Requires internet |
| 2 | database/2_concat_base |
1_hsk{1..6}.txt |
database/data/2_base.json |
Deterministic |
| 3 | database/3_enrich_word |
2_base.json |
database/data/3_enriched.json |
Claude Haiku, ~$3–6 |
| 4 | database/4_generate_database |
3_enriched.json |
src/database.ts |
Deterministic |
make db-download # Step 1 — fetch source txt files from GitHub
make db-parse # Step 2 — parse txt → 2_base.json
make db-enrich # Step 3 — enrich → 3_enriched.json (reads ANTHROPIC_API_KEY from .env.local)
make db-generate # Step 4 — generate src/database.ts
make db-pipeline # Run all 4 steps in sequence
make db-clean # Delete JSON intermediates (keeps source txt)
make db-clean-all # Delete everything including source txtWhen a word is reported missing from the search page, add it with:
make missing word=撸串This enriches the word via Claude Haiku (all fields), appends it to 3_enriched.json, regenerates database.ts, and records it with a timestamp in missing_word/data/1_added.json.
- Re-enrich specific words: delete their entries from
3_enriched.json, then re-runmake db-enrich. It skips already-enriched words. - Add new words: append to
2_base.jsonmanually, then runmake db-enrich+make db-generate. - Fix a bad entry: edit directly in
3_enriched.json, then runmake db-generate. - Full regeneration:
make db-clean-allthenmake db-pipeline. Expect ~$3–6 in Claude API costs over ~20 minutes.
Planned as an installable PWA with offline word lookup from the local word cache.