this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: cleanup h-stats

+438 -348
+1 -1
data/lang/en/characters.tsv
··· 78 78 c-00077 井 [todo: add definition] 79 79 c-00078 天 day; sky; heaven 80 80 c-00079 夫 husband; man 81 - c-00080 元 yuan (currency unit) 81 + c-00080 元 yuan; round 82 82 c-00081 無 nothing 83 83 c-00082 雲 cloud 84 84 c-00083 專 for a particular person, occasion, purpose; focused on one thing; special
+167 -167
data/lang/en/hints/meaning.characters.tsv
··· 1 1 id hant locale en 2 2 c-00001 一 ALL One line, for the number one! 3 3 c-00003 二 ALL Two lines! For the number two! 4 - c-00004 十 ALL 5 - c-00007 七 ALL 4 + c-00022 三 ALL Three lines... wait... it's this easy? 6 5 c-00009 八 ALL 7 - c-00010 人 ALL Looks like a person. See their legs. 8 - c-00011 入 ALL 9 6 c-00015 九 ALL 10 - c-00018 刀 ALL 11 7 c-00019 力 ALL Power cuts inwards, while 九 curls outwards. 12 - c-00022 三 ALL Three lines... wait... it's this easy? 13 - c-00026 工 ALL 14 - c-00027 土 ALL A plant grows in the soil. Definitely more than a stick, since it has a whole one more branch than 上 15 - c-00030 下 ALL Now it's below ground. Down! 16 - c-00031 寸 ALL 8 + c-00007 七 ALL 9 + c-00004 十 ALL 10 + c-00010 人 ALL Looks like a person. See their legs. 17 11 c-00032 大 ALL A person with outstretched arms. Now they are big! This is also how to scare away bears, apparently. 18 - c-00036 上 ALL The little stick is above ground. Up! 19 12 c-00037 小 ALL 20 - c-00038 口 ALL Looks like a mouth, no? 13 + c-00117 水 ALL Water flows between the narrow banks. 14 + c-00164 火 ALL 21 15 c-00039 山 ALL Looks like a mountain. 22 - c-00043 川 ALL A flowing river 23 - c-00045 個 ALL 16 + c-00036 上 ALL The little stick is above ground. Up! 17 + c-00030 下 ALL Now it's below ground. Down! 18 + c-00011 入 ALL 19 + c-00330 出 ALL 20 + c-00112 日 ALL 21 + c-00150 月 ALL Like a crescent moon 22 + c-00038 口 ALL Looks like a mouth, no? 23 + c-00026 工 ALL 24 + c-00262 四 ALL 25 + c-00088 五 ALL 26 + c-00160 六 ALL 27 + c-00419 早 ALL 28 + c-00119 午 ALL 24 29 c-00046 夕 ALL 25 - c-00055 門 ALL Looks like a door. 26 - c-00061 已 ALL 27 - c-00063 弓 ALL Pull back the bow, and let the arrow fly! 28 - c-00064 子 ALL 29 - c-00066 也 ALL 30 + c-00018 刀 ALL 31 + c-00027 土 ALL A plant grows in the soil. Definitely more than a stick, since it has a whole one more branch than 上 32 + c-00087 木 ALL Imagine a pine tree 33 + c-00210 本 ALL At the root of all trees is... the root? 34 + c-01018 林 ALL Two trees makes a woods 35 + c-02567 森 ALL Three trees is a whole forest! 36 + c-01237 炎 ALL Two fires make a flame 37 + c-00244 田 ALL It looks like a field or rice paddy 38 + c-00043 川 ALL A flowing river 39 + c-00216 石 ALL 石 doesn't point through like 右 does. Looks like a stone at the bottom of a hill. 40 + c-00275 白 ALL 41 + c-00236 目 ALL 30 42 c-00067 女 ALL 31 - c-00072 馬 ALL 43 + c-00064 子 ALL 44 + c-00581 好 ALL A mother and her child together are always a good thing. 45 + c-00110 少 ALL 46 + c-00507 多 ALL 47 + c-00733 男 ALL 48 + c-00217 右 ALL 49 + c-00214 左 ALL 50 + c-00113 中 ALL middle; in; center; within; among 51 + c-01087 明 ALL As bright as the sun and moon 52 + c-00174 心 ALL 32 53 c-00075 王 ALL 33 - c-00076 開 ALL 34 - c-00078 天 ALL 35 - c-00080 元 ALL 36 - c-00082 雲 ALL 37 - c-00087 木 ALL Imagine a pine tree 38 - c-00088 五 ALL 39 - c-00091 不 ALL 54 + c-00300 主 ALL 主 pokes through the top, but 王 doesn't. 55 + c-20029 后 ALL 56 + c-00513 冰 ALL Water, but with the ice radical, 冫 57 + c-01041 雨 ALL 58 + c-00100 車 ALL 59 + c-00235 旦 ALL The sun rises over the ground. You can see it rising over the flat earth. So very pictograph. 60 + c-00444 年 ALL 61 + c-00360 老 ALL 40 62 c-00093 太 ALL 41 - c-00097 友 ALL 42 - c-00100 車 ALL 43 - c-00102 牙 ALL 44 - c-00104 戈 ALL 45 - c-00105 比 ALL 46 - c-00109 止 ALL 47 - c-00110 少 ALL 48 - c-00112 日 ALL 49 - c-00113 中 ALL middle; in; center; within; among 50 - c-00116 內 ALL 51 - c-00117 水 ALL Water flows between the narrow banks. 52 63 c-00118 見 ALL 53 - c-00119 午 ALL 54 - c-00120 牛 ALL 64 + c-00283 用 ALL 55 65 c-00121 手 ALL 56 - c-00122 氣 ALL 57 66 c-00123 毛 ALL 58 - c-00127 長 ALL 59 - c-00132 化 ALL 60 - c-00141 父 ALL Damn daddy, look at that mustache! 61 - c-00144 今 ALL 62 - c-00146 分 ALL Imagine splitting 八 with a knife. You'e dividing it in half. 63 - c-00148 公 ALL 64 - c-00150 月 ALL Like a crescent moon 65 - c-00154 風 ALL Kinda looks like a kite 66 - c-00160 六 ALL 67 - c-00161 文 ALL 67 + c-00731 足 ALL 68 + c-00625 走 ALL 69 + c-00109 止 ALL 70 + c-00302 立 ALL 68 71 c-00163 方 ALL 69 - c-00164 火 ALL 70 - c-00174 心 ALL 71 - c-00176 引 ALL Keep on imagining pulling back that bow! You'll commonly see this character on door handles you're supposed to pull. 72 - c-00188 書 ALL 73 - c-00190 玉 ALL 74 - c-00204 去 ALL 75 - c-00208 古 ALL 76 - c-00210 本 ALL At the root of all trees is... the root? 77 - c-00214 左 ALL 78 - c-00216 石 ALL 石 doesn't point through like 右 does. Looks like a stone at the bottom of a hill. 79 - c-00217 右 ALL 80 - c-00222 平 ALL Looks like one of those old scales that you use to measure the weight of witches. Scales balance things. 72 + c-00227 北 ALL 73 + c-00384 西 ALL 81 74 c-00225 東 ALL 82 - c-00227 北 ALL 83 - c-00235 旦 ALL The sun rises over the ground 84 - c-00236 目 ALL 85 - c-00242 電 ALL Electricity travels down the wire 86 - c-00244 田 ALL It looks like a field or rice paddy 87 - c-00245 由 ALL 88 - c-00253 叫 ALL 89 - c-00262 四 ALL 90 - c-00263 生 ALL 91 - c-00267 禾 ALL 92 - c-00273 們 ALL 93 - c-00275 白 ALL 94 - c-00277 他 ALL 95 - c-00279 瓜 ALL 96 - c-00283 用 ALL 97 - c-00293 外 ALL 98 - c-00296 鳥 ALL Those aren't 4 feet. Those are feathers. 99 - c-00298 包 ALL 100 - c-00300 主 ALL 主 pokes through the top, but 王 doesn't. 101 - c-00302 立 ALL 102 - c-00307 半 ALL 75 + c-01422 南 ALL 76 + c-00161 文 ALL 77 + c-00078 天 ALL 78 + c-00144 今 ALL 79 + c-00072 馬 ALL 80 + c-00530 羊 ALL 81 + c-00120 牛 ALL 82 + c-02365 豬 ALL This character is two radicals put together. 83 + c-00443 肉 ALL Most commonly heard meaning animal meat that you eat. But it does mean flesh generally, so for example 果肉 is the flesh of a fruit. The middle of your apple. 84 + c-00345 母 ALL The mother has very large eyes. 85 + c-00141 父 ALL Damn daddy, look at that mustache! 86 + c-01079 果 ALL Fruits grow on trees 103 87 c-00308 汁 ALL 氵氵氵juicy! 104 - c-00330 出 ALL 105 - c-00335 加 ALL 106 - c-00336 皮 ALL 107 - c-00345 母 ALL The mother has very large eyes. 108 - c-00360 老 ALL 109 - c-00369 耳 ALL Looks like an ear 110 - c-00382 再 ALL 111 - c-00384 西 ALL 88 + c-00208 古 ALL 89 + c-00583 媽 ALL 90 + c-01171 爸 ALL 91 + c-01326 姐 ALL 92 + c-01324 妹 ALL 93 + c-00854 弟 ALL 94 + c-01852 哥 ALL 95 + c-20030 家 ALL 96 + c-00783 住 ALL 112 97 c-00388 在 ALL 113 - c-00390 有 ALL 114 - c-00419 早 ALL 98 + c-00548 字 ALL 99 + c-00066 也 ALL 100 + c-00277 他 ALL 101 + c-00582 她 ALL 102 + c-00761 我 ALL 103 + c-00782 你 ALL 104 + c-00091 不 ALL 115 105 c-00429 吃 ALL 116 - c-00443 肉 ALL 117 - c-00444 年 ALL 118 - c-00450 竹 ALL Looks like bamboo 106 + c-00307 半 ALL 107 + c-00335 加 ALL 108 + c-00578 如 ALL 109 + c-00774 但 ALL 119 110 c-00458 休 ALL A person resting under a tree. 111 + c-00784 位 ALL 112 + c-00045 個 ALL 120 113 c-00466 件 ALL 121 - c-00485 全 ALL 114 + c-00263 生 ALL 122 115 c-00505 名 ALL 123 - c-00507 多 ALL 124 - c-00513 冰 ALL Water, but with the ice radical, 冫 125 - c-00520 衣 ALL 116 + c-00055 門 ALL Looks like a door. 117 + c-00076 開 ALL 118 + c-00253 叫 ALL 119 + c-00063 弓 ALL Pull back the bow, and let the arrow fly! 120 + c-20031 干 ALL If you connect the dots, you can imagine a bit shield 121 + c-00190 玉 ALL 122 + c-01166 金 ALL 123 + c-00104 戈 ALL 124 + c-00146 分 ALL Imagine splitting 八 with a knife. You'e dividing it in half. 125 + c-00222 平 ALL Looks like one of those old scales that you use to measure the weight of witches. Scales balance things. 126 + c-02021 高 ALL 127 + c-00127 長 ALL 128 + c-01185 朋 ALL 129 + c-00097 友 ALL 130 + c-01642 美 ALL 131 + c-01081 國 ALL 132 + c-02639 喝 ALL 133 + c-02284 唱 ALL What that mouth do? It sings. 134 + c-00279 瓜 ALL 135 + c-00298 包 ALL 136 + c-20003 麵 ALL 137 + c-00105 比 ALL 138 + c-00132 化 ALL 139 + c-20032 回 ALL 140 + c-01479 是 ALL 141 + c-00293 外 ALL 142 + c-00116 內 ALL 143 + c-00204 去 ALL 144 + c-02041 站 ALL 145 + c-20033 台 ALL 146 + c-00148 公 ALL 147 + c-00690 求 ALL 148 + c-00273 們 ALL 126 149 c-00528 問 ALL 127 - c-00530 羊 ALL 128 - c-00533 米 ALL 129 - c-00536 汗 ALL 130 - c-00538 江 ALL 131 - c-00548 字 ALL 150 + c-00382 再 ALL 132 151 c-00549 安 ALL 133 - c-00578 如 ALL 134 - c-00581 好 ALL A mother and her child together are always a good thing. 135 - c-00582 她 ALL 136 - c-00583 媽 ALL 137 - c-00585 羽 ALL 138 - c-00625 走 ALL 139 - c-00665 花 ALL 140 - c-00690 求 ALL 141 - c-00731 足 ALL 142 - c-00733 男 ALL 143 - c-00761 我 ALL 144 - c-00774 但 ALL 145 - c-00782 你 ALL 146 - c-00783 住 ALL 147 - c-00784 位 ALL 148 - c-00786 身 ALL 149 - c-00797 坐 ALL 150 - c-00805 肚 ALL 151 - c-00854 弟 ALL 152 152 c-00880 牢 ALL 153 + c-00485 全 ALL 153 154 c-00883 災 ALL A flood and fire coming together to create a natural disaster 154 - c-01018 林 ALL Two trees makes a woods 155 - c-01041 雨 ALL 156 - c-01064 非 ALL 157 - c-01079 果 ALL Fruits grow on trees 158 - c-01081 國 ALL 159 - c-01087 明 ALL As bright as the sun and moon 160 - c-01166 金 ALL 161 - c-01171 爸 ALL 162 - c-01185 朋 ALL 163 - c-01190 服 ALL 155 + c-01198 狗 ALL 156 + c-02367 貓 ALL 164 157 c-01194 魚 ALL 165 - c-01198 狗 ALL 166 - c-01205 京 ALL 167 - c-01237 炎 ALL Two fires make a flame 168 - c-01248 油 ALL 169 - c-01324 妹 ALL 170 - c-01326 姐 ALL 171 - c-01389 指 ALL 172 - c-01408 草 ALL 158 + c-00296 鳥 ALL Those aren't 4 feet. Those are feathers. 159 + c-00176 引 ALL Keep on imagining pulling back that bow! You'll commonly see this character on door handles you're supposed to pull. 160 + c-00154 風 ALL Kinda looks like a kite 161 + c-00122 氣 ALL 162 + c-00082 雲 ALL 163 + c-00242 電 ALL Electricity travels down the wire 164 + c-02249 雪 ALL Rain that you can hold in your hand. 165 + c-02884 雷 ALL Lighting and thunder lighting up a field 166 + c-00390 有 ALL 167 + c-00080 元 ALL What's circular and can be spent to buy things? Moneeeeey 168 + c-00188 書 ALL 169 + c-00031 寸 ALL 170 + c-00520 衣 ALL 171 + c-01190 服 ALL 172 + c-00245 由 ALL 173 + c-02155 球 ALL 174 + c-02074 海 ALL 175 + c-01679 洋 ALL The sound comes from 羊, and the meaning comes from the water radical, 氵 176 + c-00538 江 ALL 173 177 c-01411 茶 ALL Drinking grass under a roof. Sounds like tea to me. 174 - c-01422 南 ALL 175 - c-01479 是 ALL 178 + c-00665 花 ALL 179 + c-01408 草 ALL 180 + c-02207 菜 ALL 176 181 c-01488 星 ALL 182 + c-01064 非 ALL 183 + c-00585 羽 ALL 177 184 c-01523 骨 ALL 185 + c-00805 肚 ALL 186 + c-00102 牙 ALL 187 + c-01389 指 ALL 188 + c-00336 皮 ALL 189 + c-00369 耳 ALL Looks like an ear 190 + c-00786 身 ALL 191 + c-00536 汗 ALL 192 + c-00533 米 ALL 193 + c-00267 禾 ALL 194 + c-00450 竹 ALL Looks like bamboo 195 + c-01248 油 ALL 196 + c-01205 京 ALL 197 + c-00061 已 ALL 178 198 c-01539 看 ALL 179 - c-01642 美 ALL 180 - c-01679 洋 ALL The sound comes from 羊, and the meaning comes from the water radical, 氵 181 - c-01852 哥 ALL 182 - c-02021 高 ALL 183 - c-02041 站 ALL 184 - c-02074 海 ALL 185 - c-02155 球 ALL 186 - c-02207 菜 ALL 187 - c-02249 雪 ALL Rain that you can hold in your hand. 188 - c-02284 唱 ALL What that mouth do? It sings. 189 - c-02365 豬 ALL 190 - c-02367 貓 ALL 191 - c-02567 森 ALL Three trees is a whole forest! 192 - c-02639 喝 ALL 199 + c-00797 坐 ALL 193 200 c-02681 等 ALL () 194 - c-02884 雷 ALL Lighting and thunder lighting up a field 195 - c-20003 麵 ALL 196 - c-20004 妳 ALL 197 - c-20029 后 ALL 198 - c-20030 家 ALL 199 - c-20031 干 ALL If you connect the dots, you can imagine a bit shield 200 - c-20032 回 ALL 201 - c-20033 台 ALL 201 + c-20004 妳 ALL
+16 -16
data/lang/en/hints/meaning.vocabulary.tsv
··· 5 5 v-00004 人工 ALL 6 6 v-00005 大小 ALL Big and small come together to just mean size. 7 7 v-00006 火山 ALL A fire mountain... appropriate. 8 + v-00016 山水 ALL The mountains and the water make some good scenery 8 9 v-00007 十一 ALL 10 + 1 9 10 v-00008 十九 ALL 10 + 9. You're a math wiz. 10 11 v-00009 一月 ALL The first month ··· 12 13 v-00011 入口 ALL The entry mouth is the entrance 13 14 v-00012 出口 ALL The exit mouth is the exit 14 15 v-00015 入力 ALL YOU HAVE THE POWER TO ENTER THINGS 15 - v-00016 山水 ALL The mountains and the water make some good scenery 16 16 v-00017 工人 ALL Working hard? 17 17 v-00018 大大 ALL The bigger it is, the more important it is. 18 18 v-00019 人力 ALL man + power = manpower 19 19 v-00020 上下 ALL up and down 20 20 v-00021 口水 ALL water that comes from your mouth 21 - v-00022 日出 ALL The sun goes outside in the morning. 21 + v-00022 日出 ALL Theeee sun will go out. Tomorrowwwww. 22 22 v-00023 本子 ALL The origin of all knowledge. 23 23 v-00024 多少 ALL Many + few is how many? 24 24 v-00025 明白 ALL You're not very bright, are you? ··· 31 31 v-00032 好多 ALL A good many. 32 32 v-00034 日子 ALL How many suns ago was that? 33 33 v-00035 小心 ALL You can feel your tiny little heart beating. Better be careful. 34 - v-00036 心中 ALL 35 34 v-00037 中心 ALL 36 35 v-00038 主人 ALL The master person 37 36 v-00039 好好 ALL Very nice 38 37 v-00040 男子 ALL 39 38 v-00041 女子 ALL 40 - v-00042 子女 ALL 41 - v-00043 左右 ALL "It's like when someone asks you how much and you move your left and right hands like ""mehhhhh around this much""" 39 + v-00043 左右 ALL ehhhh a little of the left, a little of the right. It's close enough. 42 40 v-00044 大多 ALL 43 - v-00045 男女 ALL 44 41 v-00046 森林 ALL That's a lot of trees. 45 42 v-00047 本人 ALL The original person is me. 46 43 v-00048 明明 ALL ··· 51 48 v-00053 女王 ALL 52 49 v-00054 少女 ALL 53 50 v-00055 白天 ALL It's bright white during the daytime. 54 - v-00056 車上 ALL 55 51 v-00057 東西 ALL "Literally east and west. But much more common to see it used to mean ""thing"". " 56 52 v-00058 火車 ALL A car. But add more fire. 57 53 v-00059 今年 ALL This year is truly the now year. ··· 82 78 v-00084 果汁 ALL Fruit + juice = fruit juice. Yum. 83 79 v-00085 老太太 ALL 84 80 v-00086 文明 ALL 85 - v-00087 大方 ALL 81 + v-00087 大方 ALL If 方 is direction, 大方 is the one who knows the direction! The big expert! 86 82 v-00088 多年 ALL 87 83 v-00089 二手 ALL Literally 2nd hand. 88 84 v-00090 手工 ALL Dem hands do work. ··· 91 87 v-00093 今日 ALL Now day is truly today. 92 88 v-00094 南北 ALL 93 89 v-00095 十足 ALL 94 - v-00096 天文 ALL 90 + v-00096 天文 ALL The study of the skies! 95 91 v-00097 一旦 ALL 96 92 v-00098 雨水 ALL 97 93 v-00099 老天 ALL ··· 151 147 v-00153 名人 ALL 152 148 v-00154 人家 ALL 153 149 v-00155 如今 ALL 154 - v-00156 上個月 ALL 150 + v-00156 上個月 ALL I've had trouble differentiating 下 and上 as past and future. My strategy is to think of a balloon. The higher it flies, you let it go further in the past. 155 151 v-00157 上門 ALL 156 - v-00158 下個月 ALL 152 + v-00158 下個月 ALL Think of a balloon. After you let it go, the higher it goes, the more it is in the past. So if it is close the the ground, we're talking about the future. 157 153 v-00159 小吃 ALL 158 154 v-00160 字母 ALL 159 155 v-00161 不止 ALL 160 156 v-00162 不足 ALL 161 157 v-00163 吃力 ALL 162 158 v-00164 加上 ALL 163 - v-00165 如下 ALL 159 + v-00165 如下 ALL if it is below 164 160 v-00166 入門 ALL 165 161 v-00167 也好 ALL 166 162 v-00168 住手 ALL ··· 251 247 v-00253 台大 ALL 252 248 v-00254 國人 ALL 253 249 v-00255 台中 ALL 254 - v-00256 生氣 ALL 250 + v-00256 生氣 ALL I took a deep breath and got a bunch of fresh air, so now I'm ready to yell it all right back at you. 255 251 v-00257 書包 ALL 256 252 v-00258 天氣 ALL 257 253 v-00259 衣服 ALL ··· 298 294 v-00300 家電 ALL 299 295 v-00301 內衣 ALL 300 296 v-00302 西元 ALL 301 - v-00303 小氣 ALL 297 + v-00303 小氣 ALL Low on breath, and low on cash. Don't breath too deeply. 302 298 v-00304 上海 ALL 303 299 v-00305 南非 ALL 304 300 v-00306 北京 ALL ··· 340 336 v-00342 未來 ALL 341 337 v-00343 玉米 ALL 342 338 v-00344 白酒 ALL 343 - v-00345 不時 ALL 339 + v-00345 不時 ALL I don't have time, so I'll stop by... occasionally 344 340 v-00346 出汗 ALL 345 341 v-00347 看出 ALL 346 342 v-00348 人間 ALL ··· 405 401 v-00407 一方面 ALL 406 402 v-00408 尺寸 ALL 407 403 v-00409 尺子 ALL 408 - v-00410 出色 ALL 404 + v-00410 出色 ALL You are so talented that you have now left the realm of color. 409 405 v-00411 點名 ALL 410 406 v-00412 電燈 ALL 411 407 v-00413 紅包 ALL ··· 496 492 v-00498 神話 ALL 497 493 v-00499 説服 ALL 498 494 v-00500 天真 ALL () 495 + v-00036 心中 ALL 496 + v-00042 子女 ALL 497 + v-00045 男女 ALL 498 + v-00056 車上 ALL
+4 -4
data/lang/en/vocabulary.tsv
··· 65 65 v-00064 明天 tomorrow 66 66 v-00065 上車 to get on; to get into 67 67 v-00066 水果 fruit 68 - v-00067 下車 to get off; to get out 68 + v-00067 下車 to get off; to get out; to get on; to enter a vehicle 69 69 v-00068 下雨 rain; to rain 70 70 v-00069 中文 Chinese; Chinese language 71 71 v-00070 北方 North ··· 321 321 v-00320 目的 purpose; objective; aim; goal; target 322 322 v-00321 平等 equality; equal 323 323 v-00322 平時 ordinarily; peacetime 324 - v-00323 全身 whole body 324 + v-00323 全身 whole body; full-body; entire body 325 325 v-00324 本來 originally; original; at first 326 326 v-00325 大夫 doctor 327 327 v-00326 工夫 laborer; time ··· 347 347 v-00346 出汗 to sweat; to perspire; sweating 348 348 v-00347 看出 to see; to find out; to perceive 349 349 v-00348 人間 human world; the earth 350 - v-00349 一身 whole body; all body 350 + v-00349 一身 whole body; full-body; entire body 351 351 v-00350 用來 be used for; used to; to be used for 352 352 v-00351 羽毛球 badminton; shuttlecock 353 353 v-00352 再次 once more; once again; one more time ··· 399 399 v-00398 目前 at present; at the present time; currently 400 400 v-00399 前後 front and back; from start to finish; around; from beginning to end; all around 401 401 v-00400 前面 front; ahead; in front; preceding 402 - v-00401 全面 total; comprehensive; all-around 402 + v-00401 全面 total; comprehensive; all-around; whole 403 403 v-00402 日常 daily; day-to-day; everyday 404 404 v-00403 上面 above; on top of; above-mentioned 405 405 v-00404 外面 outside; outdoors; outward appearance; surface; exterior
+1 -1
deno.json
··· 1 1 { 2 - "version": "3.6.0", 2 + "version": "3.6.1", 3 3 "workspace": ["./data"], 4 4 "compilerOptions": { 5 5 "lib": [
+93 -136
www/components/h-stats.ts
··· 1 1 import { html, LitElement } from 'lit' 2 2 import hsk from '$/static/gen/progress/hsk.json' with { type: 'json' } 3 3 import tocfl from '$/static/gen/progress/tocfl.json' with { type: 'json' } 4 + import jlptKanji from '$/static/gen/progress/jlpt-kanji.json' with { 5 + type: 'json', 6 + } 7 + import jlptVocab from '$/static/gen/progress/jlpt-vocab.json' with { 8 + type: 'json', 9 + } 4 10 import { SubjectType } from '$/enums.ts' 5 - import app, { Assignment } from '$/models/app.ts' 11 + import app from '$/models/app.ts' 6 12 import { Subject, subjects } from '$/models/subjects.ts' 7 13 import getString from '$/utils/get_string.ts' 8 - import { getSubjectTileProps } from '$/utils/subject_utils.ts' 14 + import { getSubjectTileProps, pallette } from '$/utils/subject_utils.ts' 15 + import { resolveLocale } from '$/utils/custom_sets.ts' 16 + import { 17 + buildStats, 18 + isLearned, 19 + parseHskTocfl, 20 + parseJlpt, 21 + type Progress, 22 + type StatsData, 23 + } from '$/utils/progress.ts' 9 24 10 25 const { Character, Radical, Vocabulary } = SubjectType 11 26 12 - type SystemType = 'HSK' | 'TOCFL' 13 - 14 - interface ProgressItem { 15 - level: number 16 - id: number 17 - traditional: string 18 - simplified: string 19 - } 20 - 21 - interface Progress { 22 - chars: ProgressItem[] 23 - vocab: ProgressItem[] 24 - } 25 - 26 - interface StatsData { 27 - chars: Record<number, { num: number; total: number }> 28 - vocab: Record<number, { num: number; total: number }> 29 - } 30 - 31 - function parseData(data: ProgressItem[]): Progress { 32 - return { 33 - chars: data.filter((row) => row.traditional.split('').length === 1), 34 - vocab: data.filter((row) => row.traditional.split('').length > 1), 35 - } 36 - } 37 - 38 - function parseStats({ chars, vocab }: Progress): StatsData { 39 - const vocabLevels: Record<string, number> = {} 40 - const charLevels: Record<string, number> = {} 41 - const stats: StatsData = { chars: {}, vocab: {} } 42 - 43 - chars.forEach(({ traditional, level }) => { 44 - stats.chars[level] ??= { num: 0, total: 0 } 45 - stats.chars[level].total++ 46 - charLevels[traditional] = level 47 - }) 48 - 49 - vocab.forEach(({ traditional, level }) => { 50 - stats.vocab[level] ??= { num: 0, total: 0 } 51 - stats.vocab[level].total++ 52 - vocabLevels[traditional] = level 53 - }) 54 - 55 - Object.values(app.assignments).forEach((assignment) => { 56 - const data = subjects.state.byId[assignment.subjectId]?.data 57 - const char = data?.character 58 - if (!char || !assignment?.startedAt) return 59 - if (char.split('').length > 1) { 60 - const level = vocabLevels[char] 61 - if (typeof level === 'number') stats.vocab[level].num++ 62 - } else { 63 - const level = charLevels[char] 64 - if (typeof level === 'number') stats.chars[level].num++ 65 - } 66 - }) 67 - 68 - return stats 69 - } 70 - 71 - function isLearned(item: ProgressItem): number { 72 - const subject = subjects.state.bySlug?.[item.traditional] 73 - return app.assignments[subject?.id]?.startedAt ? 1 : 0 74 - } 27 + type SystemType = 'HSK' | 'TOCFL' | 'JLPT' 75 28 76 29 /** 77 30 * Stats page: level progress tiles + HSK/TOCFL grade system progress. ··· 99 52 } else { 100 53 this.#currLevel = app.userLevel || 0 101 54 } 102 - this.#list = 'HSK' 55 + const locale = resolveLocale(app.locale) 56 + this.#list = locale === 'ja' ? 'JLPT' : locale === 'zh_TW' ? 'TOCFL' : 'HSK' 103 57 this.#selectedLevel = null 104 58 this.#selectedType = null 105 59 app.addEventListener(this.#onUpdate) ··· 112 66 subjects.removeEventListener(this.#onUpdate) 113 67 } 114 68 115 - #createTile(subject: Subject) { 69 + #createTile(subject: Subject, typeOverride?: SubjectType) { 116 70 const props = getSubjectTileProps(subject) 117 71 if (!props) return null 118 - const { state, color, colorClass, characters, tileType } = props 72 + const type = typeOverride ?? subject.data.type 73 + const color = type === Vocabulary 74 + ? 'purple' 75 + : type === Radical 76 + ? 'blue' 77 + : 'pink' 78 + const colorClass = (pallette[props.state] || '').replace('{color}', color) 79 + const tileType = type === Vocabulary ? 'vocabulary' : 'character' 119 80 return html` 120 81 <ui-card 121 82 type="${tileType}" 122 - state="${state}" 83 + state="${props.state}" 123 84 @click="${() => { 124 85 sessionStorage.setItem('stats-level', String(this.#currLevel)) 125 86 history.replaceState(null, '', location.pathname + location.hash) 126 87 location.href = `#!/reference/subject/${subject.id}` 127 88 }}" 128 89 class="${colorClass} font-${app.locale} ${color}-shadow" 129 - >${characters}</ui-card> 90 + >${props.characters}</ui-card> 130 91 ` 131 92 } 132 93 ··· 170 131 ` 171 132 } 172 133 173 - #renderProgressDisplay() { 174 - const data = this.#list === 'HSK' ? parseData(hsk) : parseData(tocfl) 175 - const stats = parseStats(data) 176 - const lvlName = this.#list === 'HSK' ? 'HSK' : 'lvl' 177 - const selectedProgressItems = this.#selectedType === Vocabulary 178 - ? data.vocab.filter((i) => i.level === this.#selectedLevel) 179 - : data.chars.filter((i) => i.level === this.#selectedLevel) 134 + #renderProgressDisplay(progress: Progress, stats: StatsData) { 135 + const lvlName = this.#list === 'HSK' 136 + ? 'HSK' 137 + : this.#list === 'JLPT' 138 + ? 'N' 139 + : 'lvl' 140 + const levelSort = this.#list === 'JLPT' 141 + ? (a: string, b: string) => Number(b) - Number(a) 142 + : (a: string, b: string) => Number(a) - Number(b) 143 + const selectedItems = this.#selectedType === Vocabulary 144 + ? progress.vocab.filter((i) => i.level === this.#selectedLevel) 145 + : progress.chars.filter((i) => i.level === this.#selectedLevel) 180 146 181 147 return html` 182 148 <div class="grade-system"> 183 149 <div class="stats"> 184 150 <div class="levels"> 185 151 <h5>characters</h5> 186 - ${Object.keys(stats.chars).sort().map((lvlStr) => { 152 + ${Object.keys(stats.chars).sort(levelSort).map((lvlStr) => { 187 153 const lvl = Number(lvlStr) 188 154 const { num = 0, total = 0 } = stats.chars[lvl]! 189 155 const isSelected = this.#selectedType === Character && ··· 204 170 </div> 205 171 <div class="levels"> 206 172 <h5>vocabulary</h5> 207 - ${Object.keys(stats.vocab).sort().map((lvlStr) => { 173 + ${Object.keys(stats.vocab).sort(levelSort).map((lvlStr) => { 208 174 const lvl = Number(lvlStr) 209 175 const { num = 0, total = 0 } = stats.vocab[lvl]! 210 176 const isSelected = this.#selectedType === Vocabulary && ··· 227 193 ${this.#selectedType != null && this.#selectedLevel != null 228 194 ? html` 229 195 <ui-progress-grid> 230 - ${selectedProgressItems 231 - .sort((a, b) => isLearned(a) - isLearned(b)) 196 + ${selectedItems 197 + .sort((a, b) => isLearned(a.slug) - isLearned(b.slug)) 232 198 .map((item) => { 233 - const subject = subjects.state.bySlug?.[item.traditional] 234 - return subject ? this.#createTile(subject) : null 199 + const subject = subjects.state.bySlug?.[item.slug] 200 + return subject 201 + ? this.#createTile(subject, this.#selectedType ?? undefined) 202 + : null 235 203 })} 236 204 </ui-progress-grid> 237 205 ` ··· 240 208 ` 241 209 } 242 210 211 + #renderSystemTab(system: SystemType) { 212 + return html` 213 + <h3 214 + class="${this.#list === system ? 'underline' : 'o-40'}" 215 + @click="${() => { 216 + this.#list = system 217 + this.#selectedLevel = null 218 + this.#selectedType = null 219 + this.requestUpdate() 220 + }}" 221 + > 222 + ${system} 223 + </h3> 224 + ` 225 + } 226 + 243 227 override render() { 244 228 if (!this.#currLevel) this.#currLevel = app.userLevel 245 229 const userLevel = app.userLevel 246 - const characters: Record< 247 - string, 248 - { assignment: Assignment; subject: Subject } 249 - > = {} 250 - const vocabulary: Record< 251 - string, 252 - { assignment: Assignment; subject: Subject } 253 - > = {} 254 - const radicals: Record< 255 - string, 256 - { assignment: Assignment; subject: Subject } 257 - > = {} 258 - 259 - Object.values(app.assignments).forEach((assignment) => { 260 - if (!assignment.startedAt) return 261 - const subject = subjects.state.byId[assignment.subjectId] 262 - const char = subject?.data.character 263 - if (!subject || !char) return 264 - if (subject.data.type === Character) { 265 - characters[char] = { assignment, subject } 266 - } else if (subject.data.type === Vocabulary) { 267 - vocabulary[char] = { assignment, subject } 268 - } else if (subject.data.type === Radical) { 269 - radicals[char] = { assignment, subject } 270 - } 271 - }) 230 + const locale = resolveLocale(app.locale) 231 + const progress = this.#list === 'JLPT' 232 + ? parseJlpt(jlptKanji, jlptVocab) 233 + : parseHskTocfl(this.#list === 'HSK' ? hsk : tocfl) 234 + const stats = buildStats(progress) 272 235 273 236 return html` 274 237 <div> ··· 298 261 </div> 299 262 <div class="totals"> 300 263 <h3>Total</h3> 301 - <div style="min-width: 150px"> 302 - ${getString('radicals')}: ${Object 303 - .keys(radicals).length} 304 - </div> 264 + ${stats.numRadicals 265 + ? html` 266 + <div style="min-width: 150px"> 267 + ${getString('radicals')}: ${stats.numRadicals} 268 + </div> 269 + ` 270 + : null} 305 271 <div style="min-width: 150px"> 306 - ${getString('characters')}: ${Object 307 - .keys(characters).length} 272 + ${getString('characters')}: ${stats.numChars} 308 273 </div> 309 274 <div style="min-width: 150px"> 310 - ${getString('vocabulary')}: ${Object 311 - .keys(vocabulary).length} 275 + ${getString('vocabulary')}: ${stats.numVocab} 312 276 </div> 313 277 </div> 314 278 </section> ··· 316 280 <span>${this.#renderSubjectTiles(Character)}</span> 317 281 <span>${this.#renderSubjectTiles(Vocabulary)}</span> 318 282 <div class="progress-header"> 319 - <h3 320 - class="${this.#list === 'HSK' ? 'underline' : 'o-40'}" 321 - @click="${() => { 322 - this.#list = 'HSK' 323 - this.requestUpdate() 324 - }}" 325 - > 326 - HSK 327 - </h3> 328 - <h3 329 - class="${this.#list === 'TOCFL' ? 'underline' : 'o-40'}" 330 - @click="${() => { 331 - this.#list = 'TOCFL' 332 - this.requestUpdate() 333 - }}" 334 - > 335 - TOCFL 336 - </h3> 283 + ${locale === 'ja' 284 + ? html` 285 + <h3 class="underline">JLPT</h3> 286 + ` 287 + : locale === 'zh_TW' 288 + ? html` 289 + ${this.#renderSystemTab('TOCFL')} ${this.#renderSystemTab('HSK')} 290 + ` 291 + : html` 292 + ${this.#renderSystemTab('HSK')} ${this.#renderSystemTab('TOCFL')} 293 + `} 337 294 </div> 338 - ${this.#renderProgressDisplay()} 295 + ${this.#renderProgressDisplay(progress, stats)} 339 296 </div> 340 297 ` 341 298 }
+11 -13
www/components/h-streak.ts
··· 27 27 override render() { 28 28 const { currStreak = 0, longestStreak = 0 } = app.state.stats ?? {} 29 29 return html` 30 - <section class="streak tc pa2"> 31 - <p class="f4 b ma1"> 32 - <span class="streak-count">${getString( 33 - 'studied', 34 - )} ${currStreak}</span> 35 - ${getString('days_in_a_row')}${currStreak < longestStreak 36 - ? html` 37 - <span class="o-60 f5">(${getString( 38 - 'longest_streak', 39 - )}: ${longestStreak})</span> 40 - ` 41 - : null}! 42 - </p> 30 + <section class="streak tc pa1"> 31 + <span class="streak-count">${getString( 32 + 'studied', 33 + )} ${currStreak}</span> 34 + ${getString('days_in_a_row')}${currStreak < longestStreak 35 + ? html` 36 + <span class="o-60 f5">(${getString( 37 + 'longest_streak', 38 + )}: ${longestStreak})</span> 39 + ` 40 + : null}! 43 41 </section> 44 42 ` 45 43 }
+3 -1
www/routes/games.ts
··· 1 1 import { html, LitElement } from 'lit' 2 2 import app from '$/models/app.ts' 3 3 import getString from '$/utils/get_string.ts' 4 + import { resolveLocale } from '$/utils/custom_sets.ts' 4 5 5 6 interface GameDef { 6 7 id: string ··· 52 53 } 53 54 54 55 override render() { 56 + const locale = resolveLocale(app.locale) 55 57 const available = GAMES.filter((g) => 56 - g.locales.includes(app.locale) && 58 + g.locales.includes(locale) && 57 59 (!g.userLangs || g.userLangs.includes(app.userLang)) 58 60 ) 59 61 if (!available.length) {
-1
www/routes/main.ts
··· 78 78 deck.learnable.length, 79 79 )} ${this.#renderStudyButton(SessionType.Quiz, deck.quizzable.length)} 80 80 </div> 81 - <h-streak></h-streak> 82 81 <h-stats></h-stats> 83 82 ` 84 83 }
+10 -4
www/routes/settings/custom-sets.ts
··· 39 39 } 40 40 try { 41 41 const text = await this.#file.text() 42 - const subjects = JSON.parse(text) 42 + const parsed = JSON.parse(text) 43 + const subjects = Array.isArray(parsed) ? parsed : parsed?.subjects 43 44 if (!Array.isArray(subjects)) { 44 - throw new Error('File must contain a JSON array of subjects.') 45 + throw new Error( 46 + 'File must contain a JSON array or an object with a subjects array.', 47 + ) 45 48 } 49 + const locale = Array.isArray(parsed) ? undefined : parsed?.locale 46 50 const id = toLocaleId(name) 47 - await saveCustomSet({ id, name }, subjects) 51 + await saveCustomSet({ id, name, locale }, subjects) 48 52 this.#name = '' 49 53 this.#file = null 50 54 this.#error = null ··· 83 87 (set) => 84 88 html` 85 89 <li class="item flex items-center justify-between"> 86 - <span><b>${set.name}</b> <small>(${set.id})</small></span> 90 + <span><b>${set.name}</b> <small>(${set.id}${set.locale 91 + ? ` · ${set.locale}` 92 + : ''})</small></span> 87 93 <button 88 94 class="bg-light-red" 89 95 @click="${() => this.#onDelete(set.id)}"
+5 -3
www/static/styles/theme.css
··· 234 234 user-select: none; 235 235 } 236 236 237 - main.home, 238 237 r-search, 239 238 r-main { 240 239 max-width: var(--max-width-sm); 240 + } 241 + 242 + r-search { 241 243 padding: var(--s3); 242 244 } 243 245 ··· 365 367 display: flex; 366 368 flex-direction: column; 367 369 gap: var(--s3); 368 - margin: 0 auto; 370 + margin: 0 auto var(--s4) auto; 369 371 max-width: var(--main-max-width); 370 - padding: var(--s2) 0 var(--s2) 0; 372 + padding: var(--s2) 0 var(--s4) 0; 371 373 } 372 374 373 375 .study-button,
+11 -1
www/utils/custom_sets.ts
··· 7 7 export interface CustomSet { 8 8 id: string 9 9 name: string 10 + locale?: string 10 11 } 11 12 12 13 const INDEX_KEY = 'hanzi-custom-sets' ··· 26 27 subjects: unknown[], 27 28 ): Promise<void> { 28 29 const sets = getCustomSets().filter((s) => s.id !== customSet.id) 29 - sets.push({ id: customSet.id, name: customSet.name }) 30 + sets.push({ 31 + id: customSet.id, 32 + name: customSet.name, 33 + locale: customSet.locale, 34 + }) 30 35 localStorage.setItem(INDEX_KEY, JSON.stringify(sets)) 31 36 await idbSet(DATA_PREFIX + customSet.id, subjects) 32 37 } ··· 43 48 } catch { 44 49 return null 45 50 } 51 + } 52 + 53 + /** Resolves the effective locale, following through to a custom set's declared locale if needed. */ 54 + export function resolveLocale(locale: string): string { 55 + return getCustomSets().find((s) => s.id === locale)?.locale ?? locale 46 56 } 47 57 48 58 export function toLocaleId(name: string): string {
+116
www/utils/progress.ts
··· 1 + import app from '$/models/app.ts' 2 + import { subjects } from '$/models/subjects.ts' 3 + import { SubjectType } from '$/enums.ts' 4 + 5 + const { Character, Vocabulary, Radical } = SubjectType 6 + 7 + export interface ProgressItem { 8 + level: number 9 + slug: string 10 + } 11 + 12 + export interface Progress { 13 + chars: ProgressItem[] 14 + vocab: ProgressItem[] 15 + } 16 + 17 + export interface LevelStat { 18 + num: number 19 + total: number 20 + } 21 + 22 + export interface StatsData { 23 + chars: Record<number, LevelStat> 24 + vocab: Record<number, LevelStat> 25 + numChars: number 26 + numVocab: number 27 + numRadicals: number 28 + } 29 + 30 + /** Normalize HSK/TOCFL raw data (single file, traditional/simplified fields) */ 31 + export function parseHskTocfl( 32 + raw: { level: number; traditional: string }[], 33 + ): Progress { 34 + return { 35 + chars: raw 36 + .filter((r) => [...r.traditional].length === 1) 37 + .map((r) => ({ level: r.level, slug: r.traditional })), 38 + vocab: raw 39 + .filter((r) => [...r.traditional].length > 1) 40 + .map((r) => ({ level: r.level, slug: r.traditional })), 41 + } 42 + } 43 + 44 + /** Normalize JLPT raw data (separate kanji and vocab files) */ 45 + export function parseJlpt( 46 + kanji: { level: number; kanji: string }[], 47 + vocab: { level: number; chars: string }[], 48 + ): Progress { 49 + return { 50 + chars: kanji.map((r) => ({ level: r.level, slug: r.kanji })), 51 + vocab: vocab.map((r) => ({ level: r.level, slug: r.chars })), 52 + } 53 + } 54 + 55 + /** 56 + * Single pass over all assignments: computes per-level learned counts for the 57 + * given progress system and total started subjects by type. 58 + * 59 + * Level counts use the progress system's categorization (slug lookup), so that 60 + * e.g. JLPT single-character vocab items are counted under vocab regardless of 61 + * the subject's intrinsic type. Totals use the subject's intrinsic type. 62 + */ 63 + export function buildStats(progress: Progress): StatsData { 64 + const charLevelMap = new Map<string, number>() 65 + const vocabLevelMap = new Map<string, number>() 66 + const stats: StatsData = { 67 + chars: {}, 68 + vocab: {}, 69 + numChars: 0, 70 + numVocab: 0, 71 + numRadicals: 0, 72 + } 73 + 74 + for (const { slug, level } of progress.chars) { 75 + stats.chars[level] ??= { num: 0, total: 0 } 76 + stats.chars[level].total++ 77 + charLevelMap.set(slug, level) 78 + } 79 + for (const { slug, level } of progress.vocab) { 80 + stats.vocab[level] ??= { num: 0, total: 0 } 81 + stats.vocab[level].total++ 82 + vocabLevelMap.set(slug, level) 83 + } 84 + 85 + const charSet = new Set<string>() 86 + const vocabSet = new Set<string>() 87 + const radicalSet = new Set<string>() 88 + 89 + for (const assignment of Object.values(app.assignments)) { 90 + if (!assignment.startedAt) continue 91 + const subject = subjects.state.byId[assignment.subjectId] 92 + const slug = subject?.data.slug 93 + if (!subject || !slug) continue 94 + 95 + if (subject.data.type === Character) charSet.add(slug) 96 + else if (subject.data.type === Vocabulary) vocabSet.add(slug) 97 + else if (subject.data.type === Radical) radicalSet.add(slug) 98 + 99 + const charLevel = charLevelMap.get(slug) 100 + const vocabLevel = vocabLevelMap.get(slug) 101 + if (typeof charLevel === 'number') stats.chars[charLevel].num++ 102 + else if (typeof vocabLevel === 'number') stats.vocab[vocabLevel].num++ 103 + } 104 + 105 + stats.numChars = charSet.size 106 + stats.numVocab = vocabSet.size 107 + stats.numRadicals = radicalSet.size 108 + 109 + return stats 110 + } 111 + 112 + /** Returns 1 if the subject for the given slug has been started, else 0 (for sort comparisons). */ 113 + export function isLearned(slug: string): 0 | 1 { 114 + const subject = subjects.state.bySlug?.[slug] 115 + return app.assignments[subject?.id]?.startedAt ? 1 : 0 116 + }