Hanzi Flow:自適應中文閱讀練習工具
Hanzi Flow 是一款圍繞單一構想打造的漢字練習應用:自適應句子挑選。它會依據你目前的掌握程度,從 79,000 個以上的真實例句中挑出含 2 至 5 個生字的句子,讓你維持在 90% 至 95% 的理解區間。不需管理卡片組,只要輸入拼音就能練習。
原始碼:github.com/brianhliou/hanzi-flow
現有工具的問題
中文學習應用基本上分成三類,而它們都有缺點。
像 Anki 這樣的字卡應用要你死記孤立的漢字。你花在整理卡片組的時間比真正學習的時間更多。等你終於在真實文本裡看到某個字,卻認不出來,因為你學它時完全脫離了語境。
像 Pleco 這樣的字典應用很適合查東西,但完全是被動的。你讀了、懂了,卻什麼都沒留下。沒有主動回想,也沒有輸入練習。
閱讀應用與家教給你的要嘛是童書(無聊),要嘛是母語內容(難得離譜)。你要嘛覺得乏味,要嘛被壓垮,兩者都無法培養流暢度。
缺的那一塊是維持心流。也就是那個 90% 至 95% 的理解區間,有挑戰但不至於受挫。你需要難度剛好的句子、自動挑選,並透過輸入進行主動回想。
我做了什麼
Hanzi Flow 是以句子為單位、難度自適應的練習工具。你讀中文、輸入拼音,應用會根據你對每個字的熟悉程度挑選下一句。它有 Anki 的間隔重複,卻不用管理卡片組;有 Duolingo 的練習循環,但用的是真實句子而非旅遊手冊例句。

這個應用:
- 來自 Tatoeba 的 79,000 句以上(真實用法,不是教科書例句)
- 4,000 字以上 - HSK 1 至 9 級,另加約 1,000 個 HSK 之外的漢字
- 自適應演算法 - 依你的掌握程度挑出含 2 至 5 個生字的句子
- 掌握度追蹤 - 字級分數搭配間隔重複(EWMA + SRS)
- 對應 HSK 3.0 - 可依官方課綱等級篩選(1 至 9 級或 HSK 之外)
- 字形支援 - 簡體、繁體或混合
- 即時回饋 - 彩色視覺提示,加上 1,598 個音檔(涵蓋所有拼音音節)
- 100% 本地優先 - 無帳號、無追蹤、可離線使用
你練習符合自己程度的真實句子,透過拼音輸入培養輸入流暢度,系統也會隨你進步而調整。不必手動管理卡片組。
使用體驗
首次執行:設定偏好
第一次開啟應用時,你要選兩件事:
字形類型:簡體、繁體或混合。這會篩選語料庫以符合你的目標。
HSK 等級:篩選是累積的。選「HSK 1-3」就只會得到僅使用 1、2、3 級漢字的句子。選「HSK 之外」則會納入官方課綱之外的約 1,000 個進階漢字。
你隨時可以在設定裡更改。一旦更改,應用會清空目前的句子佇列,並依新的篩選條件重新產生。

練習循環
練習很單純:
- 讀句子 - 漢字以大字顯示
- 輸入拼音 - 逐字輸入,聲調可選(
wo3或wo都可以) - 取得回饋 - 當前字以藍色標示,錯誤則紅色閃爍並重試(最多 4 次)
- 看到拼音 - 正確答案與聲調顯示在每個字下方
- 顯示翻譯 - 完成後可選擇顯示底部的英文
- 下一句 - 按空白鍵或點按按鈕
回饋既即時又視覺化。你的注意力永遠在當前的字上(藍色標示)。答錯會閃紅並播放正確發音的音訊。你有 4 次機會,之後系統會往下走,並把那個字標為紫色(放棄)。系統接受多音讀法(地 同時接受 de 與 di),但會顯示符合語境的那一個。

關鍵機制在於回饋循環。現實中,你可能在書裡看到一個字,猜錯了,卻永遠不知道答案。或者看電視時完全沒聽清發音。那樣的學習循環很慢。輕度學習者可能要花好幾個月,才能累積足夠的接觸量把一個字真正學會。
這個應用壓縮了這個循環。你看到一個字、把它打出來、立刻得到帶音訊的修正、馬上重試、繼續下一個。從頭到尾十秒。一次練習做 100 個字,就跑完 100 次學習循環。主動回想(輸入)、即時修正(音訊加視覺)與高量(句子持續出現)疊加起來,會產生被動接觸給不了的效果。
進度追蹤
統計頁面顯示四項主要指標:
- 練習過的漢字總數 - 你看過的不重複漢字
- 已掌握的漢字 - 掌握度 ≥ 0.8 的字
- 練習過的句子 - 已完成的不重複句子
- 整體正確率 - 第一次嘗試就正確的百分比
掌握度細分把漢字分成:
- 學習中(s < 0.6)- 還在建立熟悉度
- 熟練(0.6 ≤ s < 0.8)- 快到了
- 已掌握(s ≥ 0.8)- 穩固
每個字都會依間隔重複記錄下次複習的時間。


自適應演算法(NSS)
隨機挑選很糟。你會太常看到同樣的句子、在已經掌握的內容上浪費時間,或被含 10 個以上生字的句子壓垮。
Next Sentence Selection(NSS)就是挑選你下一句的演算法。它在新學、間隔重複、新鮮感與難度之間取得平衡。
核心構想
最佳區間:每句 2 至 5 個生字。
- 0 至 1 個生字 - 太簡單,只是複習
- 2 至 5 個生字 - 剛好,挑戰足夠,語境也足夠
- 6 個以上生字 - 太難,只能亂猜
演算法也會平衡:
- 新學(你從未見過的字)
- 間隔重複(複習到期的字)
- 新鮮感(不要反覆磨同樣的句子)
- 多樣性(混合不同難度)
運作方式
NSS 以批次執行。你開始練習時,它會產生 10 個句子。做完 8 個時,它會在背景預取下一批。你永遠不用等。
四個步驟:
1. 篩出符合條件的句子
從完整語料庫(79K)開始。套用你的篩選條件:
- 字形(簡體/繁體/混合)
- HSK 等級(你選的範圍)
- 冷卻時間(排除最近 20 分鐘內看過的句子)
這會得到符合條件的候選池,通常是 20K 至 40K 個句子。
2. 隨機抽取 300 個候選
從候選池隨機抽 300 個。夠大以保有多樣性,也夠小以快速計分(100 毫秒以內)。隨機抽樣確保你每次練習看到不同的句子。
3. 為每個候選計分
針對這 300 個中的每一個,依四項因素計算分數:
- 基礎收益 - 每個字的 (1 - 掌握度) 之和。掌握度低的字分數較高。若已逾期待複習,收益加倍。
- 新鮮感 - 依距上次出現的時間給加分。鼓勵多樣性,避免反覆磨同一句。
- 通過懲罰 - 如果你已多次完美通過這一句,就降低分數。避免過度練習。
- k 懲罰 - 對落在 2 至 5 個生字區間之外的句子扣分。強制維持難度。
加總起來:score = base_gain + novelty - pass_penalty - k_penalty
4. 取前 10 名、打散、排入佇列
依分數排序,取前 10 名,打散以混合難度,加入佇列。
你練習這 10 句。剩下 2 句時,演算法會在背景再跑一次,預取下一批。
掌握度追蹤
每個字都有一個 0.0 到 1.0 的掌握度分數(s)。
- s = 0.3(新字的預設值)
- s → 1.0(穩定答對)
- s → 0.0(一直答錯)
更新使用 指數加權移動平均(EWMA),α=0.15。近期表現權重較大,但變化是漸進的。一次失誤不會讓你崩盤,一次成功也不會虛胖。
當你答對一個字,它的穩定度(SRS 間隔)會乘以 1.2 倍。從 1 小時開始,接著 1.2 小時,再 1.44 小時。這讓漢字比 Anki 那種激進跳躍(1 天 → 3 天 → 7 天)留在輪替中更久。
為什麼要慢一點?因為當天複習很重要。你需要在一次練習中看到同一個字 3 至 5 次,才能把它從短期記憶搬到長期記憶。跳得太快會錯過那個窗口。
當一個字到期(超過它的 next_review_ts),它在計分中會有雙倍權重。演算法會優先挑含逾期漢字的句子。這就是經典的間隔重複。
資料處理流程
要做到這些,需要完整的資料:帶讀音的漢字、帶翻譯的句子、難度標籤、品質篩選。以下是我建立語料庫的方式。
漢字集
從 Unicode CJK 統一表意文字(U+4E00 至 U+9FFF)開始,共 20,992 個字。涵蓋現代中文的每個字,加上罕見異體與歷史字形。
接著用三個來源的資料為每個字補充內容:
- Unihan 資料庫 - 拼音讀音、異體字、部件拆解
- CC-CEDICT - 英文釋義與例詞(学 → 「study; learn」,学生 → 「student」)
- OpenCC - 簡繁對應,用於字形分類
結果:99.7% 的漢字有拼音,67% 有英文釋義,35% 有異體對應。我也知道哪些字真的出現在 Tatoeba(5,000 個以上),哪些只是理論上存在(罕見的古典用字)。

這條曲線顯示漢字如何隨 HSK 等級累積。HSK 1 約有 300 字,HSK 2 再加 300 字,以此類推。到了較高等級,曲線隨著接近約 3,000 字的識讀門檻而趨於平緩。超過之後,邊際效益遞減。
頻率分布符合 Zipf 定律:少數漢字佔絕大多數,多數字都很罕見。前 500 字涵蓋 75% 的文本,前 2,000 字涵蓋 95%。這對自適應演算法很重要,因為先學高頻字能立刻帶來閱讀能力。

HSK 分級
HSK(漢語水平考試)是中國官方的能力測驗。2021 年的修訂版(HSK 3.0)定義了 9 個等級、共 3,000 個漢字。我整合了來自 elkmovie/hsk30 的官方字表(由政府 PDF 經 OCR 取得)。
每個句子依其最難的字進行分級:
- 全是 HSK 1 的字?標為「1」
- 最難的字是 HSK 3?標為「3」
- 含有超出 HSK 9 的字?標為「beyond-hsk」
這驅動了篩選。當你選「HSK 3」,你只會看到每個字都在 1 至 3 級的句子。不會有意外。


句子處理
我從 Tatoeba 這個群眾外包翻譯資料庫取得 79,000 句以上的中文句子。每一句一邊是英文,一邊是中文。
挑戰:語境感知的拼音。漢字依語境有多種讀音。我先用 jieba 加 pypinyin 做第一輪,再用 GPT-4o-mini 驗證(79K 句約花 2 美元)。修正了 10,336 處錯誤,涵蓋常見多音字如 地(de 與 di4)、著(zhe 與 zhu4)與 谁(shei2 與 shui2)。
多音字範例:
- 了:
le(助詞)與liao3(完結) - 行:
xing2(可以)與hang2(行列) - 长:
zhang3(生長)與chang2(長)
你不能只靠查表。你得理解詞語的語境。
步驟 1:jieba + pypinyin。Jieba 把「我们试试看」切成 [“我们”, “试试”, “看”],然後 pypinyin 為每個詞產生拼音:我们试试看 → wo3 men shi4 shi4 kan4。大多情況可行,但在助詞與口語讀音上會失手。
步驟 2:GPT-4o-mini 驗證。把整個語料庫送進 OpenAI 的 API(79K 句約 2 美元,每次呼叫批次處理 10 句)。提示要求逐字輸出帶聲調標記的拼音。在 79,603 句中找到 10,336 處錯誤。主要修正:
- 覺(120 處修改):pypinyin 給
jue2(感覺),OpenAI 給jiao4(睡覺)- 你應該去睡覺了吧 -「You should go to bed」
- 著(696 處修改):pypinyin 給
zhu4(穿著),OpenAI 給zhe(助詞)- 生活就是當你忙著進行你的計劃時… -「Life is what happens when you’re busy making plans」
- 谁(404 處修改):pypinyin 給
shui2(正式),OpenAI 給shei2(口語)- 你知不知道他们是谁? -「Do you know who they are?」
- 长(136 處修改):pypinyin 給
zhang3(生長),OpenAI 給chang2(長)- 我看见一个长头发的女生 -「I saw a girl with long hair」
我人工檢視了出現最多的字,並套用高信心度的修正。OpenAI 的語境理解抓到了規則式方法漏掉的錯誤。
英文翻譯:同樣以 GPT-4o-mini 產生(總共約 1 美元)。Tatoeba 的資料集只有約 2,000 句有翻譯。若只用那些,語料庫會從 79,000 句以上縮到 2,000 句,自適應演算法的效果就毀了。所以我用 GPT-4o-mini 翻譯了全部 79,000 句以上。大型語言模型很擅長翻譯,因為它們會利用語境,而這正是準確中譯英所需要的。範例:
- 我們試試看! → Let’s give it a try!
- 你在干什麼啊? → What are you doing?
- 今天是6月18号,也是Muiriel的生日! → Today is June 18th, and it’s Muiriel’s birthday!
- 生日快乐,Muiriel! → Happy birthday, Muiriel!
- 密码是”Muiriel”。 → The password is “Muiriel.”
我也套用了品質篩選:
- 字形分類 - 簡體、繁體、中性或混合
- 粗俗語過濾 - 濾掉不雅詞語(約 10 句)
- 長度上限 - 移除超過 50 字的句子(約 270 句),避免需要滾動
- 後設句子 - 移除 Tatoeba 自我指涉的句子(約 80 句)
最終語料庫:79,333 個乾淨句子。

架構與效能
以現代網頁技術打造,針對速度與隱私最佳化。
為什麼採用本地優先?
- 隱私 - 無帳號、無追蹤、無遙測。你的進度永遠不會離開你的裝置。我連分析工具都沒有。資料由你擁有。
- 效能 - 沒有網路延遲。句子挑選在 100 毫秒內完成。輸入回饋即時。統計載入在 50 毫秒內。
- 簡單 - 沒有後端要維護、沒有資料庫要擴充、不用驗證。這個應用就是靜態檔案加上用戶端 JS。我免費託管在 Vercel。
- 取捨:沒有跨裝置同步。在筆電與手機上得分別練習。對一個 MVP 來說可以接受。大多人都有一台主要裝置。之後可以加上匯出/匯入或選用的雲端同步。
技術堆疊
前端:
- Next.js 15 搭配 App Router
- React 19 + TypeScript
- Tailwind CSS
- 以 Dexie.js 操作 IndexedDB
資料處理流程:
- Python 3.9+ 用於處理腳本
- jieba 用於斷詞
- pypinyin 用於語境感知拼音
- pandas + matplotlib 用於分析
- GPT-4o-mini 用於翻譯與拼音驗證
音訊:
- AWS Polly(Zhiyu 語音)用於語音合成
- 1,598 個音節,涵蓋所有聲調
- 採用 OGG 格式以相容網頁瀏覽器
部署:
- Vercel(零設定)
- 靜態網站,無伺服器
- 全球 CDN
資料載入與快取
應用在首次造訪時載入大型資料集,之後積極快取。
初始載入:
- 79,333 個句子(約 41MB JSON)- 只取一次,存在記憶體中
- 漢字對應表 CSV(約 2MB)- ID、拼音、釋義
- 音檔 - 依需求取用(每個 1 至 2KB)
記憶體內快取:
- 句子存在 JavaScript 陣列中
- 漢字到 ID 的對應存在 Map 中(O(1) 查詢)
- 練習期間不會重新取用
IndexedDB 持久化:
- WordMastery 表 - 每個練習過的字一列(掌握度分數、SRS 間隔、下次複習時間、次數)
- SentenceProgress 表 - 每個完成的句子一列(通過率、嘗試次數、上次出現時間)
- SentenceQueue 表 - 儲存接下來的 10 個句子(更改偏好時重新產生)
所有資料都留在你的裝置上。除了初始載入,不會有任何網路呼叫。
效能技巧:
- 音訊預載 - NSS 挑好下一句時,會在背景預載該句的拼音音訊(3 至 5 個檔案,小於 10KB)。等你看到它時,音訊已經快取好了。
- 佇列預取 - 當你做完 10 句中的 8 句,NSS 會在背景執行,準備下一批。你永遠不用等。
- 延遲載入 - 統計頁面只在你造訪時才計算分布,而非每次操作都算。
離線: 首次載入後,完全可離線使用。在飛機上、地鐵裡,任何地方都能練。進度會立刻存入 IndexedDB(不需要儲存按鈕)。
關鍵設計決策
聲調數字與聲調符號
應用接受聲調數字(wo3、shi4)而非聲調符號(wǒ、shì)。為什麼?
- 更容易輸入(不需特殊鍵盤)
- 是中文輸入法的標準做法(為真正打字做準備)
- 沒有歧義(
v與ü等價,聲調符號需要 Unicode 組合字元) - 迫使你明確思考聲調(
wo3是第三聲),而非靠視覺記憶
以句子為基礎的學習
為什麼用句子而不是孤立的詞?
- 語境 - 在真實用法中學漢字。「我们试试看」教你 我们(我們)、试试(試試)、看(看)以及語法結構。
- 閱讀練習 - 模擬真實閱讀。培養解析漢字序列的能力。
- 輸入流暢度 - 為完整句子訓練肌肉記憶,就像你實際會用的那樣。
- 難度校準 - 句子有天然的難度分布(生字數量)。詞級做不到這點。
- 動機 - 完成一個句子會有進展感。你在讀真正的中文,不是在磨「的 的 的」的字卡。
接下來
這個應用在日常練習上功能已經完整。如果我再回來做,可能新增:
- 行動版 PWA(讓它可安裝)
- HSK 統計細分
- 複習模式(以 SRS 為主的練習)
- 句子音訊(完整發音,而非僅字級)
- 漢字卡(字源、筆順、合成詞)
- 聲調分析(追蹤你容易混淆的聲調)
- 自訂語料庫(匯入你自己的句子)
- 匯出/匯入(跨裝置可攜性)
這是開源專案。有想法嗎?開個 issue 或 PR。
結語
我做這個是為了解決自己的需求。我想練中文閱讀,又不想背負字卡的管理成本。真實句子、自適應難度、乾淨的輸入介面。
結果變成一場對資料處理流程(79K 句以上、語境感知拼音)、自適應演算法(EWMA + SRS)與本地優先架構(IndexedDB + 快取)的深入探索。我對多音字、Zipf 定律,以及如何為當天複習調校間隔重複學到很多。
如果你在學中文,試試看。如果你是開發者,看看這個儲存庫。句子挑選演算法、資料處理與本地優先做法裡都有些有趣的東西。
感謝:Tatoeba 提供句子、MDBG 提供 CC-CEDICT、elkmovie 提供 HSK 3.0 字表、Unicode 聯盟提供 Unihan。
自己試試
造訪 hanzi.brianhliou.com 開始。
- 選擇你的字形與 HSK 等級
- 用聲調數字輸入拼音(
wo3、ni3、ta1) - 到統計頁面查看進度
- 每天練習,看掌握度攀升
原始碼在 GitHub,採用 MIT 授權。複製後在本機執行:
git clone https://github.com/brianhliou/hanzi-flow.git
cd hanzi-flow/app
npm install
npm run dev
開啟 http://localhost:3000。