learned-kanji 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ohzono
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # learned-kanji
2
+
3
+ Check whether Japanese text uses only the kanji that have been taught by a given school grade in Japan.
4
+
5
+ 日本の学習指導要領に基づき、文章が「その学年までに習う漢字」だけで書かれているかを判定します。
6
+
7
+ - Based on the official tables: 学年別漢字配当表 (1026 kanji, grades 1–6) and 常用漢字表 (2136 kanji)
8
+ - Zero dependencies, ~16 KB, works in browsers and Node.js (ESM / CJS, TypeScript types included)
9
+ - Fast: one pass over the string with a `Uint8Array` lookup, no regular expressions
10
+
11
+ ```sh
12
+ npm install --save-dev learned-kanji
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { isLearnedBy, unlearnedKanji } from 'learned-kanji';
19
+
20
+ isLearnedBy('山と川', 1); // true
21
+ isLearnedBy('海', 1); // false (海 is taught in grade 2)
22
+ isLearnedBy('憂鬱な語彙', 9); // true
23
+ isLearnedBy('薔薇', 9); // false
24
+
25
+ unlearnedKanji('薔薇と海', 1); // ['薔', '薇', '海']
26
+ ```
27
+
28
+ In a test, use `unlearnedKanji` so that a failure tells you which characters are wrong:
29
+
30
+ ```ts
31
+ import { expect, test } from 'vitest';
32
+ import { unlearnedKanji } from 'learned-kanji';
33
+ import messages from '../src/locales/ja.json';
34
+
35
+ test('UI text uses only kanji taught by the end of junior high school', () => {
36
+ for (const [key, text] of Object.entries(messages)) {
37
+ expect(unlearnedKanji(text, 9), key).toEqual([]);
38
+ }
39
+ });
40
+ ```
41
+
42
+ ## Grades
43
+
44
+ | `grade` | School year | Kanji allowed |
45
+ | --- | --- | --- |
46
+ | `1` – `6` | 小学校 1–6 年 | Cumulative 学年別漢字配当表: 80 / 240 / 440 / 642 / 835 / 1026 |
47
+ | `7` – `9` | 中学校 1–3 年 | All of 常用漢字表: 2136 |
48
+
49
+ The grade is inclusive: `isLearnedBy(text, 3)` means "taught by the end of grade 3".
50
+
51
+ **7, 8 and 9 are the same set.** 中学校学習指導要領 does not allocate kanji to individual grades; it only says that students learn to read most of the 常用漢字 by the end of grade 9. There is no official data that tells grade 7 from grade 8, so this package does not invent one.
52
+
53
+ ## API
54
+
55
+ ### `isLearnedBy(text, grade, options?): boolean`
56
+
57
+ `true` when every kanji in `text` has been taught by the end of `grade`. Stops at the first offending character.
58
+
59
+ ### `unlearnedKanji(text, grade, options?): string[]`
60
+
61
+ The kanji that have not been taught by the end of `grade`: unique, in order of appearance.
62
+
63
+ ### `levelOfKanji(char, options?): 1 | 2 | 3 | 4 | 5 | 6 | 'secondary' | undefined`
64
+
65
+ Where a single kanji is first taught. `'secondary'` means 常用漢字 outside the 配当表; `undefined` means it is not taught (or is not a single kanji).
66
+
67
+ ### `kanjiLearnedBy(grade): string[]`
68
+
69
+ All kanji taught by the end of `grade`, in the order of the official tables.
70
+
71
+ ### `options.strict`
72
+
73
+ 常用漢字表 prints four characters in a form that differs from the one normally typed: 𠮟 塡 剝 頰. Almost all real text uses 叱 填 剥 頬 instead, so **both forms are accepted by default**. Pass `{ strict: true }` to accept only the official forms.
74
+
75
+ Note that 𠮟 is U+20B9F, outside the BMP. It is handled correctly.
76
+
77
+ ### What is ignored
78
+
79
+ Only kanji are judged. Hiragana, katakana, Latin letters, digits, punctuation, emoji and the iteration mark 々 always pass. Every CJK ideograph that is not in the tables (CJK Unified Ideographs, Extension A, Extension B and later, compatibility ideographs) fails.
80
+
81
+ Radical characters (CJK Radicals Supplement and Kangxi Radicals, U+2E80–U+2FDF) also fail. They look identical to real kanji — `⼭` U+2F2D renders like `山` — and usually get into text through copy and paste from PDFs, so letting them pass would defeat the check.
82
+
83
+ The text is not normalized. Enclosed and squared forms such as ㈱ ㊙ ㍻ are treated as symbols and pass; call `text.normalize('NFKC')` first if you want them judged as 株 秘 平成.
84
+
85
+ Readings are not considered: a kanji counts as learned from the grade it is allocated to, even if a particular reading is taught later.
86
+
87
+ ## Complexity and limits
88
+
89
+ - `isLearnedBy` is O(n) time and O(1) memory: one pass, one table lookup per character, no allocation, and it returns at the first offending kanji. About 3 ms per million characters on an M-series Mac.
90
+ - `unlearnedKanji` is O(n) time. Its memory is bounded by the number of distinct offending kanji, not by the length of the text.
91
+ - There is no length limit other than the maximum string length of the JavaScript engine (about 2^29 characters in V8). No regular expressions are used, so there is no backtracking and no ReDoS risk with untrusted input.
92
+ - Importing the module builds a 21 KB table once (2140 writes).
93
+ - `text` must be a string; this is enforced by the TypeScript types only. An invalid `grade` throws a `RangeError`.
94
+
95
+ ## Data sources
96
+
97
+ - 文部科学省「小学校学習指導要領(平成29年告示)」国語 別表「学年別漢字配当表」, in force since April 2020
98
+ - 文化庁「常用漢字表」(平成22年内閣告示第2号)
99
+
100
+ The grade table was checked against two independent transcriptions and against the list of changes published in 文部科学省「小学校学習指導要領(平成29年告示)解説 国語編」 (20 kanji added, 25 prefecture kanji in grade 4, 32 kanji moved). The 常用漢字 list was checked against two independent transcriptions. The character counts are fixed by tests.
101
+
102
+ Some older packages still ship the previous table (1006 kanji). This one does not.
103
+
104
+ ## License
105
+
106
+ MIT. The kanji tables themselves are published by the Japanese government.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Source data. Edit this file only when the official tables are revised.
3
+ *
4
+ * - GRADE_1..6: 文部科学省「小学校学習指導要領(平成29年告示)」国語 別表「学年別漢字配当表」
5
+ * (in force since April 2020; 1026 characters, listed in the official order)
6
+ * - SECONDARY: 文化庁「常用漢字表」(平成22年内閣告示第2号, 2136 characters) minus the 1026 above.
7
+ * 中学校学習指導要領 does not allocate these to individual grades.
8
+ */
9
+ /** 第1学年 (80字) */
10
+ export declare const GRADE_1: string;
11
+ /** 第2学年 (160字) */
12
+ export declare const GRADE_2: string;
13
+ /** 第3学年 (200字) */
14
+ export declare const GRADE_3: string;
15
+ /** 第4学年 (202字) */
16
+ export declare const GRADE_4: string;
17
+ /** 第5学年 (193字) */
18
+ export declare const GRADE_5: string;
19
+ /** 第6学年 (191字) */
20
+ export declare const GRADE_6: string;
21
+ /** 常用漢字のうち学年別漢字配当表にない字 (1110字) */
22
+ export declare const SECONDARY: string;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ /**
3
+ * Source data. Edit this file only when the official tables are revised.
4
+ *
5
+ * - GRADE_1..6: 文部科学省「小学校学習指導要領(平成29年告示)」国語 別表「学年別漢字配当表」
6
+ * (in force since April 2020; 1026 characters, listed in the official order)
7
+ * - SECONDARY: 文化庁「常用漢字表」(平成22年内閣告示第2号, 2136 characters) minus the 1026 above.
8
+ * 中学校学習指導要領 does not allocate these to individual grades.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.SECONDARY = exports.GRADE_6 = exports.GRADE_5 = exports.GRADE_4 = exports.GRADE_3 = exports.GRADE_2 = exports.GRADE_1 = void 0;
12
+ /** 第1学年 (80字) */
13
+ exports.GRADE_1 = '一右雨円王音下火花貝学気九休玉金空月犬見五口校左三山子四糸字耳七車手十出女小上森' +
14
+ '人水正生青夕石赤千川先早草足村大男竹中虫町天田土二日入年白八百文木本名目立力林六';
15
+ /** 第2学年 (160字) */
16
+ exports.GRADE_2 = '引羽雲園遠何科夏家歌画回会海絵外角楽活間丸岩顔汽記帰弓牛魚京強教近兄形計元言原戸' +
17
+ '古午後語工公広交光考行高黄合谷国黒今才細作算止市矢姉思紙寺自時室社弱首秋週春書少' +
18
+ '場色食心新親図数西声星晴切雪船線前組走多太体台地池知茶昼長鳥朝直通弟店点電刀冬当' +
19
+ '東答頭同道読内南肉馬売買麦半番父風分聞米歩母方北毎妹万明鳴毛門夜野友用曜来里理話';
20
+ /** 第3学年 (200字) */
21
+ exports.GRADE_3 = '悪安暗医委意育員院飲運泳駅央横屋温化荷界開階寒感漢館岸起期客究急級宮球去橋業曲局' +
22
+ '銀区苦具君係軽血決研県庫湖向幸港号根祭皿仕死使始指歯詩次事持式実写者主守取酒受州' +
23
+ '拾終習集住重宿所暑助昭消商章勝乗植申身神真深進世整昔全相送想息速族他打対待代第題' +
24
+ '炭短談着注柱丁帳調追定庭笛鉄転都度投豆島湯登等動童農波配倍箱畑発反坂板皮悲美鼻筆' +
25
+ '氷表秒病品負部服福物平返勉放味命面問役薬由油有遊予羊洋葉陽様落流旅両緑礼列練路和';
26
+ /** 第4学年 (202字) */
27
+ exports.GRADE_4 = '愛案以衣位茨印英栄媛塩岡億加果貨課芽賀改械害街各覚潟完官管関観願岐希季旗器機議求' +
28
+ '泣給挙漁共協鏡競極熊訓軍郡群径景芸欠結建健験固功好香候康佐差菜最埼材崎昨札刷察参' +
29
+ '産散残氏司試児治滋辞鹿失借種周祝順初松笑唱焼照城縄臣信井成省清静席積折節説浅戦選' +
30
+ '然争倉巣束側続卒孫帯隊達単置仲沖兆低底的典伝徒努灯働特徳栃奈梨熱念敗梅博阪飯飛必' +
31
+ '票標不夫付府阜富副兵別辺変便包法望牧末満未民無約勇要養浴利陸良料量輪類令冷例連老' +
32
+ '労録';
33
+ /** 第5学年 (193字) */
34
+ exports.GRADE_5 = '圧囲移因永営衛易益液演応往桜可仮価河過快解格確額刊幹慣眼紀基寄規喜技義逆久旧救居' +
35
+ '許境均禁句型経潔件険検限現減故個護効厚耕航鉱構興講告混査再災妻採際在財罪殺雑酸賛' +
36
+ '士支史志枝師資飼示似識質舎謝授修述術準序招証象賞条状常情織職制性政勢精製税責績接' +
37
+ '設絶祖素総造像増則測属率損貸態団断築貯張停提程適統堂銅導得毒独任燃能破犯判版比肥' +
38
+ '非費備評貧布婦武復複仏粉編弁保墓報豊防貿暴脈務夢迷綿輸余容略留領歴';
39
+ /** 第6学年 (191字) */
40
+ exports.GRADE_6 = '胃異遺域宇映延沿恩我灰拡革閣割株干巻看簡危机揮貴疑吸供胸郷勤筋系敬警劇激穴券絹権' +
41
+ '憲源厳己呼誤后孝皇紅降鋼刻穀骨困砂座済裁策冊蚕至私姿視詞誌磁射捨尺若樹収宗就衆従' +
42
+ '縦縮熟純処署諸除承将傷障蒸針仁垂推寸盛聖誠舌宣専泉洗染銭善奏窓創装層操蔵臓存尊退' +
43
+ '宅担探誕段暖値宙忠著庁頂腸潮賃痛敵展討党糖届難乳認納脳派拝背肺俳班晩否批秘俵腹奮' +
44
+ '並陛閉片補暮宝訪亡忘棒枚幕密盟模訳郵優預幼欲翌乱卵覧裏律臨朗論';
45
+ /** 常用漢字のうち学年別漢字配当表にない字 (1110字) */
46
+ exports.SECONDARY = '亜哀挨曖握扱宛嵐依威為畏尉萎偉椅彙違維慰緯壱逸芋咽姻淫陰隠韻唄鬱畝浦詠影鋭疫悦越' +
47
+ '謁閲炎怨宴援煙猿鉛縁艶汚凹押旺欧殴翁奥憶臆虞乙俺卸穏佳苛架華菓渦嫁暇禍靴寡箇稼蚊' +
48
+ '牙瓦雅餓介戒怪拐悔皆塊楷潰壊懐諧劾崖涯慨蓋該概骸垣柿核殻郭較隔獲嚇穫岳顎掛括喝渇' +
49
+ '葛滑褐轄且釜鎌刈甘汗缶肝冠陥乾勘患貫喚堪換敢棺款閑勧寛歓監緩憾還環韓艦鑑含玩頑企' +
50
+ '伎忌奇祈軌既飢鬼亀幾棋棄毀畿輝騎宜偽欺儀戯擬犠菊吉喫詰却脚虐及丘朽臼糾嗅窮巨拒拠' +
51
+ '虚距御凶叫狂享況峡挟狭恐恭脅矯響驚仰暁凝巾斤菌琴僅緊錦謹襟吟駆惧愚偶遇隅串屈掘窟' +
52
+ '繰勲薫刑茎契恵啓掲渓蛍傾携継詣慶憬稽憩鶏迎鯨隙撃桁傑肩倹兼剣拳軒圏堅嫌献遣賢謙鍵' +
53
+ '繭顕懸幻玄弦舷股虎孤弧枯雇誇鼓錮顧互呉娯悟碁勾孔巧甲江坑抗攻更拘肯侯恒洪荒郊貢控' +
54
+ '梗喉慌硬絞項溝綱酵稿衡購乞拷剛傲豪克酷獄駒込頃昆恨婚痕紺魂墾懇沙唆詐鎖挫采砕宰栽' +
55
+ '彩斎債催塞歳載剤削柵索酢搾錯咲刹拶撮擦桟惨傘斬暫旨伺刺祉肢施恣脂紫嗣雌摯賜諮侍慈' +
56
+ '餌璽軸𠮟疾執湿嫉漆芝赦斜煮遮邪蛇酌釈爵寂朱狩殊珠腫趣寿呪需儒囚舟秀臭袖羞愁酬醜蹴' +
57
+ '襲汁充柔渋銃獣叔淑粛塾俊瞬旬巡盾准殉循潤遵庶緒如叙徐升召匠床抄肖尚昇沼宵症祥称渉' +
58
+ '紹訟掌晶焦硝粧詔奨詳彰憧衝償礁鐘丈冗浄剰畳壌嬢錠譲醸拭殖飾触嘱辱尻伸芯辛侵津唇娠' +
59
+ '振浸紳診寝慎審震薪刃尽迅甚陣尋腎須吹炊帥粋衰酔遂睡穂随髄枢崇据杉裾瀬是姓征斉牲凄' +
60
+ '逝婿誓請醒斥析脊隻惜戚跡籍拙窃摂仙占扇栓旋煎羨腺詮践箋潜遷薦繊鮮禅漸膳繕狙阻租措' +
61
+ '粗疎訴塑遡礎双壮荘捜挿桑掃曹曽爽喪痩葬僧遭槽踪燥霜騒藻憎贈即促捉俗賊遜汰妥唾堕惰' +
62
+ '駄耐怠胎泰堆袋逮替滞戴滝択沢卓拓託濯諾濁但脱奪棚誰丹旦胆淡嘆端綻鍛弾壇恥致遅痴稚' +
63
+ '緻畜逐蓄秩窒嫡抽衷酎鋳駐弔挑彫眺釣貼超跳徴嘲澄聴懲勅捗沈珍朕陳鎮椎墜塚漬坪爪鶴呈' +
64
+ '廷抵邸亭貞帝訂逓偵堤艇締諦泥摘滴溺迭哲徹撤添塡殿斗吐妬途渡塗賭奴怒到逃倒凍唐桃透' +
65
+ '悼盗陶塔搭棟痘筒稲踏謄藤闘騰洞胴瞳峠匿督篤凸突屯豚頓貪鈍曇丼那謎鍋軟尼弐匂虹尿妊' +
66
+ '忍寧捻粘悩濃把覇婆罵杯排廃輩培陪媒賠伯拍泊迫剝舶薄漠縛爆箸肌鉢髪伐抜罰閥氾帆汎伴' +
67
+ '畔般販斑搬煩頒範繁藩蛮盤妃彼披卑疲被扉碑罷避尾眉微膝肘匹泌姫漂苗描猫浜賓頻敏瓶扶' +
68
+ '怖附訃赴浮符普腐敷膚賦譜侮舞封伏幅覆払沸紛雰噴墳憤丙併柄塀幣弊蔽餅壁璧癖蔑偏遍哺' +
69
+ '捕舗募慕簿芳邦奉抱泡胞俸倣峰砲崩蜂飽褒縫乏忙坊妨房肪某冒剖紡傍帽貌膨謀頰朴睦僕墨' +
70
+ '撲没勃堀奔翻凡盆麻摩磨魔昧埋膜枕又抹慢漫魅岬蜜妙眠矛霧娘冥銘滅免麺茂妄盲耗猛網黙' +
71
+ '紋冶弥厄躍闇喩愉諭癒唯幽悠湧猶裕雄誘憂融与誉妖庸揚揺溶腰瘍踊窯擁謡抑沃翼拉裸羅雷' +
72
+ '頼絡酪辣濫藍欄吏痢履璃離慄柳竜粒隆硫侶虜慮了涼猟陵僚寮療瞭糧厘倫隣瑠涙累塁励戻鈴' +
73
+ '零霊隷齢麗暦劣烈裂恋廉錬呂炉賂露弄郎浪廊楼漏籠麓賄脇惑枠湾腕';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * School year in Japan: 1–6 = 小学校, 7–9 = 中学校1–3年.
3
+ *
4
+ * 中学校学習指導要領 does not allocate kanji to individual grades, so 7, 8 and 9
5
+ * all mean the same set: the whole 常用漢字表 (2136 characters).
6
+ */
7
+ export type Grade = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
8
+ /** Where a kanji is first taught: an elementary grade, or `'secondary'` (常用漢字 outside the 配当表). */
9
+ export type KanjiLevel = 1 | 2 | 3 | 4 | 5 | 6 | 'secondary';
10
+ export interface Options {
11
+ /**
12
+ * Accept only the glyphs printed in 常用漢字表. By default the four everyday
13
+ * variants 叱 填 剥 頬 are treated like their official forms 𠮟 塡 剝 頰.
14
+ */
15
+ strict?: boolean;
16
+ }
17
+ /** Tables this version is built from. */
18
+ export declare const SOURCE: {
19
+ readonly elementary: "小学校学習指導要領(平成29年告示)国語 別表 学年別漢字配当表";
20
+ readonly secondary: "常用漢字表(平成22年内閣告示第2号)";
21
+ };
22
+ /**
23
+ * True when every kanji in `text` has been taught by the end of `grade`.
24
+ * Kana, Latin letters, digits, punctuation and 々 are ignored.
25
+ */
26
+ export declare function isLearnedBy(text: string, grade: Grade, options?: Options): boolean;
27
+ /** Kanji in `text` that have not been taught by the end of `grade`, unique, in order of appearance. */
28
+ export declare function unlearnedKanji(text: string, grade: Grade, options?: Options): string[];
29
+ /** Where `char` is first taught, or `undefined` when it is not a taught kanji. */
30
+ export declare function levelOfKanji(char: string, options?: Options): KanjiLevel | undefined;
31
+ /** All kanji taught by the end of `grade` (cumulative), in the order of the official tables. */
32
+ export declare function kanjiLearnedBy(grade: Grade): string[];
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SOURCE = void 0;
4
+ exports.isLearnedBy = isLearnedBy;
5
+ exports.unlearnedKanji = unlearnedKanji;
6
+ exports.levelOfKanji = levelOfKanji;
7
+ exports.kanjiLearnedBy = kanjiLearnedBy;
8
+ const data_ts_1 = require("./data.js");
9
+ /** Tables this version is built from. */
10
+ exports.SOURCE = {
11
+ elementary: '小学校学習指導要領(平成29年告示)国語 別表 学年別漢字配当表',
12
+ secondary: '常用漢字表(平成22年内閣告示第2号)',
13
+ };
14
+ const BASE = 0x4e00;
15
+ const END = 0x9fff;
16
+ const RADICALS = 0x2e80;
17
+ const RADICALS_END = 0x2fdf;
18
+ const SECONDARY_LEVEL = 7;
19
+ const VARIANT = 0x10;
20
+ // 𠮟 is the only 常用漢字 outside the BMP.
21
+ const SHIKARU = 0x20b9f;
22
+ const GRADES = [data_ts_1.GRADE_1, data_ts_1.GRADE_2, data_ts_1.GRADE_3, data_ts_1.GRADE_4, data_ts_1.GRADE_5, data_ts_1.GRADE_6];
23
+ // table[codePoint - BASE]: 0 = not taught, 1–6 = grade, 7 = secondary, 7|VARIANT = everyday variant
24
+ const table = /* @__PURE__ */ buildTable();
25
+ function buildTable() {
26
+ const t = new Uint8Array(END - BASE + 1);
27
+ GRADES.forEach((chars, i) => {
28
+ for (const ch of chars)
29
+ t[ch.codePointAt(0) - BASE] = i + 1;
30
+ });
31
+ for (const ch of data_ts_1.SECONDARY) {
32
+ const cp = ch.codePointAt(0);
33
+ if (cp !== SHIKARU)
34
+ t[cp - BASE] = SECONDARY_LEVEL;
35
+ }
36
+ for (const ch of '叱填剥頬')
37
+ t[ch.codePointAt(0) - BASE] = SECONDARY_LEVEL | VARIANT;
38
+ return t;
39
+ }
40
+ function limitOf(grade) {
41
+ if (!Number.isInteger(grade) || grade < 1 || grade > 9) {
42
+ throw new RangeError(`grade must be an integer from 1 to 9, got ${String(grade)}`);
43
+ }
44
+ return grade > 6 ? SECONDARY_LEVEL : grade;
45
+ }
46
+ /** Level of a code point outside the table range: 0 = kanji that is not taught, 7 = 𠮟, -1 = not a kanji. */
47
+ function levelOutsideTable(cp) {
48
+ if (cp === SHIKARU)
49
+ return SECONDARY_LEVEL;
50
+ // Radicals (CJK Radicals Supplement, Kangxi Radicals: look-alikes such as ⼭ U+2F2D for 山),
51
+ // Extension A, compatibility ideographs, Extension B and beyond
52
+ if ((cp >= RADICALS && cp <= RADICALS_END) ||
53
+ (cp >= 0x3400 && cp <= 0x4dbf) ||
54
+ (cp >= 0xf900 && cp <= 0xfaff) ||
55
+ (cp >= 0x20000 && cp <= 0x3ffff)) {
56
+ return 0;
57
+ }
58
+ return -1;
59
+ }
60
+ function levelOf(cp, mask) {
61
+ return cp >= BASE && cp <= END ? table[cp - BASE] & mask : levelOutsideTable(cp);
62
+ }
63
+ function maskOf(options) {
64
+ return options?.strict ? 0xff : 0x0f;
65
+ }
66
+ /**
67
+ * The single scanning loop. Collects offending code points into `found`, or
68
+ * returns false at the first one when `found` is null. O(n), no allocation.
69
+ */
70
+ function scan(text, limit, mask, found) {
71
+ for (let i = 0; i < text.length; i++) {
72
+ const c = text.charCodeAt(i);
73
+ if (c < RADICALS)
74
+ continue; // ASCII and other non-CJK text
75
+ let cp = c;
76
+ let v;
77
+ if (c >= BASE && c <= END) {
78
+ v = table[c - BASE] & mask;
79
+ }
80
+ else if (c > RADICALS_END && c < 0x3400) {
81
+ continue; // kana and CJK punctuation
82
+ }
83
+ else {
84
+ if (c >= 0xd800 && c <= 0xdbff) {
85
+ cp = text.codePointAt(i);
86
+ if (cp > 0xffff)
87
+ i++;
88
+ }
89
+ v = levelOutsideTable(cp);
90
+ }
91
+ if (v === 0 || v > limit) {
92
+ if (found === null)
93
+ return false;
94
+ found.add(cp);
95
+ }
96
+ }
97
+ return found === null || found.size === 0;
98
+ }
99
+ /**
100
+ * True when every kanji in `text` has been taught by the end of `grade`.
101
+ * Kana, Latin letters, digits, punctuation and 々 are ignored.
102
+ */
103
+ function isLearnedBy(text, grade, options) {
104
+ return scan(text, limitOf(grade), maskOf(options), null);
105
+ }
106
+ /** Kanji in `text` that have not been taught by the end of `grade`, unique, in order of appearance. */
107
+ function unlearnedKanji(text, grade, options) {
108
+ const found = new Set();
109
+ scan(text, limitOf(grade), maskOf(options), found);
110
+ return Array.from(found, (cp) => String.fromCodePoint(cp));
111
+ }
112
+ /** Where `char` is first taught, or `undefined` when it is not a taught kanji. */
113
+ function levelOfKanji(char, options) {
114
+ const cp = char.codePointAt(0);
115
+ if (cp === undefined || char.length !== (cp > 0xffff ? 2 : 1))
116
+ return undefined;
117
+ const v = levelOf(cp, maskOf(options));
118
+ if (v < 1 || v > SECONDARY_LEVEL)
119
+ return undefined;
120
+ return v === SECONDARY_LEVEL ? 'secondary' : v;
121
+ }
122
+ /** All kanji taught by the end of `grade` (cumulative), in the order of the official tables. */
123
+ function kanjiLearnedBy(grade) {
124
+ const limit = limitOf(grade);
125
+ const chars = GRADES.slice(0, Math.min(limit, 6)).join('') + (limit === SECONDARY_LEVEL ? data_ts_1.SECONDARY : '');
126
+ return [...chars];
127
+ }
@@ -0,0 +1 @@
1
+ { "type": "commonjs" }
package/dist/data.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Source data. Edit this file only when the official tables are revised.
3
+ *
4
+ * - GRADE_1..6: 文部科学省「小学校学習指導要領(平成29年告示)」国語 別表「学年別漢字配当表」
5
+ * (in force since April 2020; 1026 characters, listed in the official order)
6
+ * - SECONDARY: 文化庁「常用漢字表」(平成22年内閣告示第2号, 2136 characters) minus the 1026 above.
7
+ * 中学校学習指導要領 does not allocate these to individual grades.
8
+ */
9
+ /** 第1学年 (80字) */
10
+ export declare const GRADE_1: string;
11
+ /** 第2学年 (160字) */
12
+ export declare const GRADE_2: string;
13
+ /** 第3学年 (200字) */
14
+ export declare const GRADE_3: string;
15
+ /** 第4学年 (202字) */
16
+ export declare const GRADE_4: string;
17
+ /** 第5学年 (193字) */
18
+ export declare const GRADE_5: string;
19
+ /** 第6学年 (191字) */
20
+ export declare const GRADE_6: string;
21
+ /** 常用漢字のうち学年別漢字配当表にない字 (1110字) */
22
+ export declare const SECONDARY: string;
package/dist/data.js ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Source data. Edit this file only when the official tables are revised.
3
+ *
4
+ * - GRADE_1..6: 文部科学省「小学校学習指導要領(平成29年告示)」国語 別表「学年別漢字配当表」
5
+ * (in force since April 2020; 1026 characters, listed in the official order)
6
+ * - SECONDARY: 文化庁「常用漢字表」(平成22年内閣告示第2号, 2136 characters) minus the 1026 above.
7
+ * 中学校学習指導要領 does not allocate these to individual grades.
8
+ */
9
+ /** 第1学年 (80字) */
10
+ export const GRADE_1 = '一右雨円王音下火花貝学気九休玉金空月犬見五口校左三山子四糸字耳七車手十出女小上森' +
11
+ '人水正生青夕石赤千川先早草足村大男竹中虫町天田土二日入年白八百文木本名目立力林六';
12
+ /** 第2学年 (160字) */
13
+ export const GRADE_2 = '引羽雲園遠何科夏家歌画回会海絵外角楽活間丸岩顔汽記帰弓牛魚京強教近兄形計元言原戸' +
14
+ '古午後語工公広交光考行高黄合谷国黒今才細作算止市矢姉思紙寺自時室社弱首秋週春書少' +
15
+ '場色食心新親図数西声星晴切雪船線前組走多太体台地池知茶昼長鳥朝直通弟店点電刀冬当' +
16
+ '東答頭同道読内南肉馬売買麦半番父風分聞米歩母方北毎妹万明鳴毛門夜野友用曜来里理話';
17
+ /** 第3学年 (200字) */
18
+ export const GRADE_3 = '悪安暗医委意育員院飲運泳駅央横屋温化荷界開階寒感漢館岸起期客究急級宮球去橋業曲局' +
19
+ '銀区苦具君係軽血決研県庫湖向幸港号根祭皿仕死使始指歯詩次事持式実写者主守取酒受州' +
20
+ '拾終習集住重宿所暑助昭消商章勝乗植申身神真深進世整昔全相送想息速族他打対待代第題' +
21
+ '炭短談着注柱丁帳調追定庭笛鉄転都度投豆島湯登等動童農波配倍箱畑発反坂板皮悲美鼻筆' +
22
+ '氷表秒病品負部服福物平返勉放味命面問役薬由油有遊予羊洋葉陽様落流旅両緑礼列練路和';
23
+ /** 第4学年 (202字) */
24
+ export const GRADE_4 = '愛案以衣位茨印英栄媛塩岡億加果貨課芽賀改械害街各覚潟完官管関観願岐希季旗器機議求' +
25
+ '泣給挙漁共協鏡競極熊訓軍郡群径景芸欠結建健験固功好香候康佐差菜最埼材崎昨札刷察参' +
26
+ '産散残氏司試児治滋辞鹿失借種周祝順初松笑唱焼照城縄臣信井成省清静席積折節説浅戦選' +
27
+ '然争倉巣束側続卒孫帯隊達単置仲沖兆低底的典伝徒努灯働特徳栃奈梨熱念敗梅博阪飯飛必' +
28
+ '票標不夫付府阜富副兵別辺変便包法望牧末満未民無約勇要養浴利陸良料量輪類令冷例連老' +
29
+ '労録';
30
+ /** 第5学年 (193字) */
31
+ export const GRADE_5 = '圧囲移因永営衛易益液演応往桜可仮価河過快解格確額刊幹慣眼紀基寄規喜技義逆久旧救居' +
32
+ '許境均禁句型経潔件険検限現減故個護効厚耕航鉱構興講告混査再災妻採際在財罪殺雑酸賛' +
33
+ '士支史志枝師資飼示似識質舎謝授修述術準序招証象賞条状常情織職制性政勢精製税責績接' +
34
+ '設絶祖素総造像増則測属率損貸態団断築貯張停提程適統堂銅導得毒独任燃能破犯判版比肥' +
35
+ '非費備評貧布婦武復複仏粉編弁保墓報豊防貿暴脈務夢迷綿輸余容略留領歴';
36
+ /** 第6学年 (191字) */
37
+ export const GRADE_6 = '胃異遺域宇映延沿恩我灰拡革閣割株干巻看簡危机揮貴疑吸供胸郷勤筋系敬警劇激穴券絹権' +
38
+ '憲源厳己呼誤后孝皇紅降鋼刻穀骨困砂座済裁策冊蚕至私姿視詞誌磁射捨尺若樹収宗就衆従' +
39
+ '縦縮熟純処署諸除承将傷障蒸針仁垂推寸盛聖誠舌宣専泉洗染銭善奏窓創装層操蔵臓存尊退' +
40
+ '宅担探誕段暖値宙忠著庁頂腸潮賃痛敵展討党糖届難乳認納脳派拝背肺俳班晩否批秘俵腹奮' +
41
+ '並陛閉片補暮宝訪亡忘棒枚幕密盟模訳郵優預幼欲翌乱卵覧裏律臨朗論';
42
+ /** 常用漢字のうち学年別漢字配当表にない字 (1110字) */
43
+ export const SECONDARY = '亜哀挨曖握扱宛嵐依威為畏尉萎偉椅彙違維慰緯壱逸芋咽姻淫陰隠韻唄鬱畝浦詠影鋭疫悦越' +
44
+ '謁閲炎怨宴援煙猿鉛縁艶汚凹押旺欧殴翁奥憶臆虞乙俺卸穏佳苛架華菓渦嫁暇禍靴寡箇稼蚊' +
45
+ '牙瓦雅餓介戒怪拐悔皆塊楷潰壊懐諧劾崖涯慨蓋該概骸垣柿核殻郭較隔獲嚇穫岳顎掛括喝渇' +
46
+ '葛滑褐轄且釜鎌刈甘汗缶肝冠陥乾勘患貫喚堪換敢棺款閑勧寛歓監緩憾還環韓艦鑑含玩頑企' +
47
+ '伎忌奇祈軌既飢鬼亀幾棋棄毀畿輝騎宜偽欺儀戯擬犠菊吉喫詰却脚虐及丘朽臼糾嗅窮巨拒拠' +
48
+ '虚距御凶叫狂享況峡挟狭恐恭脅矯響驚仰暁凝巾斤菌琴僅緊錦謹襟吟駆惧愚偶遇隅串屈掘窟' +
49
+ '繰勲薫刑茎契恵啓掲渓蛍傾携継詣慶憬稽憩鶏迎鯨隙撃桁傑肩倹兼剣拳軒圏堅嫌献遣賢謙鍵' +
50
+ '繭顕懸幻玄弦舷股虎孤弧枯雇誇鼓錮顧互呉娯悟碁勾孔巧甲江坑抗攻更拘肯侯恒洪荒郊貢控' +
51
+ '梗喉慌硬絞項溝綱酵稿衡購乞拷剛傲豪克酷獄駒込頃昆恨婚痕紺魂墾懇沙唆詐鎖挫采砕宰栽' +
52
+ '彩斎債催塞歳載剤削柵索酢搾錯咲刹拶撮擦桟惨傘斬暫旨伺刺祉肢施恣脂紫嗣雌摯賜諮侍慈' +
53
+ '餌璽軸𠮟疾執湿嫉漆芝赦斜煮遮邪蛇酌釈爵寂朱狩殊珠腫趣寿呪需儒囚舟秀臭袖羞愁酬醜蹴' +
54
+ '襲汁充柔渋銃獣叔淑粛塾俊瞬旬巡盾准殉循潤遵庶緒如叙徐升召匠床抄肖尚昇沼宵症祥称渉' +
55
+ '紹訟掌晶焦硝粧詔奨詳彰憧衝償礁鐘丈冗浄剰畳壌嬢錠譲醸拭殖飾触嘱辱尻伸芯辛侵津唇娠' +
56
+ '振浸紳診寝慎審震薪刃尽迅甚陣尋腎須吹炊帥粋衰酔遂睡穂随髄枢崇据杉裾瀬是姓征斉牲凄' +
57
+ '逝婿誓請醒斥析脊隻惜戚跡籍拙窃摂仙占扇栓旋煎羨腺詮践箋潜遷薦繊鮮禅漸膳繕狙阻租措' +
58
+ '粗疎訴塑遡礎双壮荘捜挿桑掃曹曽爽喪痩葬僧遭槽踪燥霜騒藻憎贈即促捉俗賊遜汰妥唾堕惰' +
59
+ '駄耐怠胎泰堆袋逮替滞戴滝択沢卓拓託濯諾濁但脱奪棚誰丹旦胆淡嘆端綻鍛弾壇恥致遅痴稚' +
60
+ '緻畜逐蓄秩窒嫡抽衷酎鋳駐弔挑彫眺釣貼超跳徴嘲澄聴懲勅捗沈珍朕陳鎮椎墜塚漬坪爪鶴呈' +
61
+ '廷抵邸亭貞帝訂逓偵堤艇締諦泥摘滴溺迭哲徹撤添塡殿斗吐妬途渡塗賭奴怒到逃倒凍唐桃透' +
62
+ '悼盗陶塔搭棟痘筒稲踏謄藤闘騰洞胴瞳峠匿督篤凸突屯豚頓貪鈍曇丼那謎鍋軟尼弐匂虹尿妊' +
63
+ '忍寧捻粘悩濃把覇婆罵杯排廃輩培陪媒賠伯拍泊迫剝舶薄漠縛爆箸肌鉢髪伐抜罰閥氾帆汎伴' +
64
+ '畔般販斑搬煩頒範繁藩蛮盤妃彼披卑疲被扉碑罷避尾眉微膝肘匹泌姫漂苗描猫浜賓頻敏瓶扶' +
65
+ '怖附訃赴浮符普腐敷膚賦譜侮舞封伏幅覆払沸紛雰噴墳憤丙併柄塀幣弊蔽餅壁璧癖蔑偏遍哺' +
66
+ '捕舗募慕簿芳邦奉抱泡胞俸倣峰砲崩蜂飽褒縫乏忙坊妨房肪某冒剖紡傍帽貌膨謀頰朴睦僕墨' +
67
+ '撲没勃堀奔翻凡盆麻摩磨魔昧埋膜枕又抹慢漫魅岬蜜妙眠矛霧娘冥銘滅免麺茂妄盲耗猛網黙' +
68
+ '紋冶弥厄躍闇喩愉諭癒唯幽悠湧猶裕雄誘憂融与誉妖庸揚揺溶腰瘍踊窯擁謡抑沃翼拉裸羅雷' +
69
+ '頼絡酪辣濫藍欄吏痢履璃離慄柳竜粒隆硫侶虜慮了涼猟陵僚寮療瞭糧厘倫隣瑠涙累塁励戻鈴' +
70
+ '零霊隷齢麗暦劣烈裂恋廉錬呂炉賂露弄郎浪廊楼漏籠麓賄脇惑枠湾腕';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * School year in Japan: 1–6 = 小学校, 7–9 = 中学校1–3年.
3
+ *
4
+ * 中学校学習指導要領 does not allocate kanji to individual grades, so 7, 8 and 9
5
+ * all mean the same set: the whole 常用漢字表 (2136 characters).
6
+ */
7
+ export type Grade = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
8
+ /** Where a kanji is first taught: an elementary grade, or `'secondary'` (常用漢字 outside the 配当表). */
9
+ export type KanjiLevel = 1 | 2 | 3 | 4 | 5 | 6 | 'secondary';
10
+ export interface Options {
11
+ /**
12
+ * Accept only the glyphs printed in 常用漢字表. By default the four everyday
13
+ * variants 叱 填 剥 頬 are treated like their official forms 𠮟 塡 剝 頰.
14
+ */
15
+ strict?: boolean;
16
+ }
17
+ /** Tables this version is built from. */
18
+ export declare const SOURCE: {
19
+ readonly elementary: "小学校学習指導要領(平成29年告示)国語 別表 学年別漢字配当表";
20
+ readonly secondary: "常用漢字表(平成22年内閣告示第2号)";
21
+ };
22
+ /**
23
+ * True when every kanji in `text` has been taught by the end of `grade`.
24
+ * Kana, Latin letters, digits, punctuation and 々 are ignored.
25
+ */
26
+ export declare function isLearnedBy(text: string, grade: Grade, options?: Options): boolean;
27
+ /** Kanji in `text` that have not been taught by the end of `grade`, unique, in order of appearance. */
28
+ export declare function unlearnedKanji(text: string, grade: Grade, options?: Options): string[];
29
+ /** Where `char` is first taught, or `undefined` when it is not a taught kanji. */
30
+ export declare function levelOfKanji(char: string, options?: Options): KanjiLevel | undefined;
31
+ /** All kanji taught by the end of `grade` (cumulative), in the order of the official tables. */
32
+ export declare function kanjiLearnedBy(grade: Grade): string[];
package/dist/index.js ADDED
@@ -0,0 +1,120 @@
1
+ import { GRADE_1, GRADE_2, GRADE_3, GRADE_4, GRADE_5, GRADE_6, SECONDARY } from "./data.js";
2
+ /** Tables this version is built from. */
3
+ export const SOURCE = {
4
+ elementary: '小学校学習指導要領(平成29年告示)国語 別表 学年別漢字配当表',
5
+ secondary: '常用漢字表(平成22年内閣告示第2号)',
6
+ };
7
+ const BASE = 0x4e00;
8
+ const END = 0x9fff;
9
+ const RADICALS = 0x2e80;
10
+ const RADICALS_END = 0x2fdf;
11
+ const SECONDARY_LEVEL = 7;
12
+ const VARIANT = 0x10;
13
+ // 𠮟 is the only 常用漢字 outside the BMP.
14
+ const SHIKARU = 0x20b9f;
15
+ const GRADES = [GRADE_1, GRADE_2, GRADE_3, GRADE_4, GRADE_5, GRADE_6];
16
+ // table[codePoint - BASE]: 0 = not taught, 1–6 = grade, 7 = secondary, 7|VARIANT = everyday variant
17
+ const table = /* @__PURE__ */ buildTable();
18
+ function buildTable() {
19
+ const t = new Uint8Array(END - BASE + 1);
20
+ GRADES.forEach((chars, i) => {
21
+ for (const ch of chars)
22
+ t[ch.codePointAt(0) - BASE] = i + 1;
23
+ });
24
+ for (const ch of SECONDARY) {
25
+ const cp = ch.codePointAt(0);
26
+ if (cp !== SHIKARU)
27
+ t[cp - BASE] = SECONDARY_LEVEL;
28
+ }
29
+ for (const ch of '叱填剥頬')
30
+ t[ch.codePointAt(0) - BASE] = SECONDARY_LEVEL | VARIANT;
31
+ return t;
32
+ }
33
+ function limitOf(grade) {
34
+ if (!Number.isInteger(grade) || grade < 1 || grade > 9) {
35
+ throw new RangeError(`grade must be an integer from 1 to 9, got ${String(grade)}`);
36
+ }
37
+ return grade > 6 ? SECONDARY_LEVEL : grade;
38
+ }
39
+ /** Level of a code point outside the table range: 0 = kanji that is not taught, 7 = 𠮟, -1 = not a kanji. */
40
+ function levelOutsideTable(cp) {
41
+ if (cp === SHIKARU)
42
+ return SECONDARY_LEVEL;
43
+ // Radicals (CJK Radicals Supplement, Kangxi Radicals: look-alikes such as ⼭ U+2F2D for 山),
44
+ // Extension A, compatibility ideographs, Extension B and beyond
45
+ if ((cp >= RADICALS && cp <= RADICALS_END) ||
46
+ (cp >= 0x3400 && cp <= 0x4dbf) ||
47
+ (cp >= 0xf900 && cp <= 0xfaff) ||
48
+ (cp >= 0x20000 && cp <= 0x3ffff)) {
49
+ return 0;
50
+ }
51
+ return -1;
52
+ }
53
+ function levelOf(cp, mask) {
54
+ return cp >= BASE && cp <= END ? table[cp - BASE] & mask : levelOutsideTable(cp);
55
+ }
56
+ function maskOf(options) {
57
+ return options?.strict ? 0xff : 0x0f;
58
+ }
59
+ /**
60
+ * The single scanning loop. Collects offending code points into `found`, or
61
+ * returns false at the first one when `found` is null. O(n), no allocation.
62
+ */
63
+ function scan(text, limit, mask, found) {
64
+ for (let i = 0; i < text.length; i++) {
65
+ const c = text.charCodeAt(i);
66
+ if (c < RADICALS)
67
+ continue; // ASCII and other non-CJK text
68
+ let cp = c;
69
+ let v;
70
+ if (c >= BASE && c <= END) {
71
+ v = table[c - BASE] & mask;
72
+ }
73
+ else if (c > RADICALS_END && c < 0x3400) {
74
+ continue; // kana and CJK punctuation
75
+ }
76
+ else {
77
+ if (c >= 0xd800 && c <= 0xdbff) {
78
+ cp = text.codePointAt(i);
79
+ if (cp > 0xffff)
80
+ i++;
81
+ }
82
+ v = levelOutsideTable(cp);
83
+ }
84
+ if (v === 0 || v > limit) {
85
+ if (found === null)
86
+ return false;
87
+ found.add(cp);
88
+ }
89
+ }
90
+ return found === null || found.size === 0;
91
+ }
92
+ /**
93
+ * True when every kanji in `text` has been taught by the end of `grade`.
94
+ * Kana, Latin letters, digits, punctuation and 々 are ignored.
95
+ */
96
+ export function isLearnedBy(text, grade, options) {
97
+ return scan(text, limitOf(grade), maskOf(options), null);
98
+ }
99
+ /** Kanji in `text` that have not been taught by the end of `grade`, unique, in order of appearance. */
100
+ export function unlearnedKanji(text, grade, options) {
101
+ const found = new Set();
102
+ scan(text, limitOf(grade), maskOf(options), found);
103
+ return Array.from(found, (cp) => String.fromCodePoint(cp));
104
+ }
105
+ /** Where `char` is first taught, or `undefined` when it is not a taught kanji. */
106
+ export function levelOfKanji(char, options) {
107
+ const cp = char.codePointAt(0);
108
+ if (cp === undefined || char.length !== (cp > 0xffff ? 2 : 1))
109
+ return undefined;
110
+ const v = levelOf(cp, maskOf(options));
111
+ if (v < 1 || v > SECONDARY_LEVEL)
112
+ return undefined;
113
+ return v === SECONDARY_LEVEL ? 'secondary' : v;
114
+ }
115
+ /** All kanji taught by the end of `grade` (cumulative), in the order of the official tables. */
116
+ export function kanjiLearnedBy(grade) {
117
+ const limit = limitOf(grade);
118
+ const chars = GRADES.slice(0, Math.min(limit, 6)).join('') + (limit === SECONDARY_LEVEL ? SECONDARY : '');
119
+ return [...chars];
120
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "learned-kanji",
3
+ "version": "0.1.0",
4
+ "description": "Check whether Japanese text uses only the kanji taught by a given school grade in Japan (学年別漢字配当表 / 常用漢字表). Zero dependencies, fast, works in browsers.",
5
+ "keywords": [
6
+ "kanji",
7
+ "japanese",
8
+ "kyoiku-kanji",
9
+ "joyo-kanji",
10
+ "教育漢字",
11
+ "常用漢字",
12
+ "学年別漢字配当表",
13
+ "grade",
14
+ "school",
15
+ "validation",
16
+ "test"
17
+ ],
18
+ "license": "MIT",
19
+ "author": "ohzono",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/ohzono/learned-kanji.git"
23
+ },
24
+ "homepage": "https://github.com/ohzono/learned-kanji#readme",
25
+ "bugs": "https://github.com/ohzono/learned-kanji/issues",
26
+ "type": "module",
27
+ "sideEffects": false,
28
+ "main": "./dist/cjs/index.js",
29
+ "module": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "import": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ },
37
+ "require": {
38
+ "types": "./dist/cjs/index.d.ts",
39
+ "default": "./dist/cjs/index.js"
40
+ }
41
+ }
42
+ },
43
+ "files": [
44
+ "dist"
45
+ ],
46
+ "scripts": {
47
+ "build": "node scripts/build.mjs",
48
+ "typecheck": "tsc -p tsconfig.json --noEmit",
49
+ "test": "node --test \"test/*.test.ts\"",
50
+ "test:dist": "node --test test/smoke/smoke.test.mjs",
51
+ "bench": "node bench.mjs",
52
+ "prepublishOnly": "npm run typecheck && npm test && npm run build && npm run test:dist"
53
+ },
54
+ "devDependencies": {
55
+ "typescript": "~5.9.3"
56
+ }
57
+ }