@kensio/pinyinjs 1.2.0 → 1.2.2

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +111 -16
  2. package/README.md +35 -35
  3. package/data/core.entries +89 -89
  4. package/data/full.entries +257 -257
  5. package/data/manifest.json +6 -6
  6. package/data/standard.entries +102 -102
  7. package/dist/decode/convert.d.ts.map +1 -1
  8. package/dist/decode/convert.js +91 -40
  9. package/dist/decode/convert.js.map +1 -1
  10. package/dist/decode/decode.d.ts +12 -2
  11. package/dist/decode/decode.d.ts.map +1 -1
  12. package/dist/decode/decode.js +64 -7
  13. package/dist/decode/decode.js.map +1 -1
  14. package/dist/decode/reading-rules.d.ts +57 -0
  15. package/dist/decode/reading-rules.d.ts.map +1 -1
  16. package/dist/decode/reading-rules.js +178 -1
  17. package/dist/decode/reading-rules.js.map +1 -1
  18. package/dist/dictionary/assertions.d.ts.map +1 -1
  19. package/dist/dictionary/assertions.js +20 -0
  20. package/dist/dictionary/assertions.js.map +1 -1
  21. package/dist/dictionary/locale.d.ts +55 -0
  22. package/dist/dictionary/locale.d.ts.map +1 -0
  23. package/dist/dictionary/locale.js +323 -0
  24. package/dist/dictionary/locale.js.map +1 -0
  25. package/dist/dictionary/merge.d.ts +3 -1
  26. package/dist/dictionary/merge.d.ts.map +1 -1
  27. package/dist/dictionary/merge.js +54 -10
  28. package/dist/dictionary/merge.js.map +1 -1
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +2 -2
  32. package/dist/index.js.map +1 -1
  33. package/dist/numerals/cardinal.d.ts +15 -1
  34. package/dist/numerals/cardinal.d.ts.map +1 -1
  35. package/dist/numerals/cardinal.js +8 -1
  36. package/dist/numerals/cardinal.js.map +1 -1
  37. package/dist/numerals/characters.d.ts +9 -0
  38. package/dist/numerals/characters.d.ts.map +1 -1
  39. package/dist/numerals/characters.js +18 -0
  40. package/dist/numerals/characters.js.map +1 -1
  41. package/dist/numerals/numerals.d.ts.map +1 -1
  42. package/dist/numerals/numerals.js +12 -4
  43. package/dist/numerals/numerals.js.map +1 -1
  44. package/dist/numerals/text.d.ts +23 -9
  45. package/dist/numerals/text.d.ts.map +1 -1
  46. package/dist/numerals/text.js +93 -9
  47. package/dist/numerals/text.js.map +1 -1
  48. package/dist/orthography/capitals.d.ts.map +1 -1
  49. package/dist/orthography/capitals.js +25 -8
  50. package/dist/orthography/capitals.js.map +1 -1
  51. package/package.json +1 -1
@@ -1,3 +1,5 @@
1
+ import { QUANTITY_CHARACTERS } from "../numerals/characters.js";
2
+ import { toCharacters } from "../script/characters.js";
1
3
  import { tagOf, wordEndingAt, wordStartingAt, } from "./rules.js";
2
4
  /**
3
5
  * Whether a syllable is `děi`, the modal reading of 得.
@@ -100,8 +102,183 @@ export const ATTESTED_ERHUA = {
100
102
  return (reading?.at(-1)?.erhua ?? false) ? "forbid" : "keep";
101
103
  },
102
104
  };
105
+ /**
106
+ * Whether a syllable is `jiāo`, the verbal reading of 教.
107
+ */
108
+ function isJiao(syllable) {
109
+ return (syllable?.initial === "j" && syllable.final === "iao" && syllable.tone === 1);
110
+ }
111
+ /**
112
+ * What a 教 that is teaching governs: the thing or the person taught.
113
+ *
114
+ * A pronoun, a common noun, a name or one of the tags jieba gives a subject —
115
+ * 教我, 教英语, 教历史, 教孩子, 教玛丽. `ns` is deliberately absent, since the
116
+ * only 教 with a place tag after it over the corpus is 教青局, an office.
117
+ */
118
+ const TAUGHT_TAGS = new Set(["r", "n", "nz", "nr", "nt", "ng"]);
119
+ /**
120
+ * What follows a 教 that is teaching where no object does: aspect and the
121
+ * complement marker, which only a verb takes.
122
+ *
123
+ * 他教了三年书, 他教过我英语, 她教书教得很快乐. Matched as characters rather
124
+ * than through their tags, because what the dictionary has starting at a 得 is
125
+ * as likely to be 得很 as the particle itself.
126
+ */
127
+ const ASPECT = new Set(["了", "过", "過", "着", "著", "得"]);
128
+ /**
129
+ * The tag prefix jieba gives a particle, which nothing teaching can follow.
130
+ */
131
+ const PARTICLE_TAG = "u";
132
+ /**
133
+ * Whether the 教 at a position is teaching something to somebody.
134
+ *
135
+ * The object is what says so, which is the same shape the modal 得 rule takes:
136
+ * a 教 with a pronoun, a noun or a name after it is governing it, and a 教 with
137
+ * 了, 过, 着 or 得 after it is a verb whatever follows that. A particle in front
138
+ * of it rules it out, since a verb does not follow one.
139
+ */
140
+ function isTeachingAt(context, at) {
141
+ if (tagOf(context, wordEndingAt(context, at)).startsWith(PARTICLE_TAG)) {
142
+ return false;
143
+ }
144
+ return (ASPECT.has(context.characters[at + 1] ?? "") ||
145
+ TAUGHT_TAGS.has(tagOf(context, wordStartingAt(context, at + 1))));
146
+ }
147
+ /**
148
+ * 教 read as `jiāo` where it is teaching rather than a religion.
149
+ *
150
+ * The dictionary stores 教 as `jiào` with `jiāo` as an alternate, and `jiào` is
151
+ * right for the compounds — 教育, 教师, 宗教, 主教 — which are words and reach
152
+ * their reading through the word. What is left is the 教 that stands as a word
153
+ * of its own, and that one is the verb: 他在北京大学教了三年书 came out
154
+ * `jiàole sān nián shū`, and 我教英语, 谁教你法语 and 她教我如何游泳 with it.
155
+ * 教书 is a word and was always `jiāo shū`; nothing carried that across a 了.
156
+ *
157
+ * See {@link isTeachingAt} for what decides it. Forcing the single-character
158
+ * edge is not enough on its own, because a reading spanning two characters
159
+ * carries its own 教 into the position: 王老师教我们汉语 read `jiào` off 师教,
160
+ * and 来教我 off 来教. Those are pairs the dictionary holds with no part of
161
+ * speech and at the cost of a word nothing has ever counted — a reading rather
162
+ * than a word — so a *tagged* word ending in 教 is left alone, which is every
163
+ * one that matters: 任教, 宗教, 主教, 佛教, 传教, 执教, 请教, 家教.
164
+ *
165
+ * Measured over 88,866 lines of Tatoeba and zh.wikipedia, 181 教 decode as a
166
+ * word of their own and every one of them read `jiào`. This moves 158 to
167
+ * `jiāo` and is wrong on three: 统一教创始人, 方法教深思 and 做到了教政分离,
168
+ * where a nominal compound takes an object's shape, the last of them because
169
+ * 做到了 is a word and so the 了 in front is not a particle to look at. Of the
170
+ * 21 it leaves, 8 are right — 诸教中, 教外别传, 董教总, 风教, 教青局 twice, a
171
+ * 教室 reached through a bad split, and a 教 used as a bare noun — and 13 are
172
+ * misses, where jieba tags the object something this does not name.
173
+ */
174
+ export const TEACHING_JIAO = {
175
+ name: "teaching-jiao",
176
+ verdictFor: (context) => {
177
+ const { edge } = context;
178
+ if (edge.text === "教") {
179
+ return isJiao(edge.reading[0]) && isTeachingAt(context, edge.from)
180
+ ? "force"
181
+ : "keep";
182
+ }
183
+ return edge.partOfSpeech === "" &&
184
+ edge.text.endsWith("教") &&
185
+ edge.reading.length === edge.to - edge.from &&
186
+ !isJiao(edge.reading.at(-1)) &&
187
+ isTeachingAt(context, edge.to - 1)
188
+ ? "forbid"
189
+ : "keep";
190
+ },
191
+ };
192
+ /**
193
+ * The tag the dictionary gives a 量词.
194
+ */
195
+ const MEASURE = "q";
196
+ /**
197
+ * The mark that makes the number after it a position rather than a count.
198
+ *
199
+ * 第三集团军 is the Third Army Group and not three 集团军, so the 集 the
200
+ * dictionary tags `q` is counting nothing and 集团军 is a word like any other.
201
+ */
202
+ const ORDINAL = "第";
203
+ /**
204
+ * Where the number counting the character at a position starts, or undefined
205
+ * where nothing counts it.
206
+ *
207
+ * Two things stop a numeral in front of a 量词 from counting it. An ordinal
208
+ * names a position, so 第十级别 is the tenth 级别 rather than ten 级; and a
209
+ * numeral inside a longer word belongs to that word, so the 一 of 唯一道路 is
210
+ * counting no 道 and the 二 of 十二月 no 月.
211
+ */
212
+ function countedFrom(context, at) {
213
+ const { characters } = context;
214
+ let from = at;
215
+ while (from > 0 && QUANTITY_CHARACTERS.has(characters[from - 1] ?? "")) {
216
+ from--;
217
+ }
218
+ if (from === at || characters[from - 1] === ORDINAL) {
219
+ return undefined;
220
+ }
221
+ const before = wordEndingAt(context, at);
222
+ const held = before === undefined ? at : at - toCharacters(before).length;
223
+ return held < from ? undefined : from;
224
+ }
225
+ /**
226
+ * A 量词 with a number in front of it does not start the next word.
227
+ *
228
+ * 三个人 is three people and came out `sān gèrén`, three *personals*, because
229
+ * 个人 is a common noun and nothing weighed it against the 个 belonging to the
230
+ * 三 in front of it. The same swallow takes 一杯水 into 杯水, 两家俱乐部 into
231
+ * 家俱 and 一天天 into 天天; digits reach it too, since a run decoded after a
232
+ * number sees that number — see `decodeRun` — so 2个人 is the same case.
233
+ *
234
+ * What makes it decidable is that the dictionary tags the 量词: 个, 次, 天, 位,
235
+ * 杯 and 匹 are `q`, while the characters that only look like measure words in
236
+ * this position are not — 分 is `v`, 部 and 成 are `n`, 年 and 点 are `m`. So
237
+ * 五分钟, 三部分, 五成分, 五年级 and 三点钟 are untouched, and those are exactly
238
+ * the words a blanket rule would break. A word that is itself a 量词 is kept as
239
+ * well, since 分钟 and 点钟 are units rather than something being counted.
240
+ *
241
+ * Only the edge *starting at the 量词* is forbidden, so a number and its
242
+ * measure written solid survive whole where the dictionary has them: 一辈子 and
243
+ * 一会儿 are entries covering the number too, and the decode still reaches them.
244
+ *
245
+ * Measured over the same 88,866 lines of Tatoeba and zh.wikipedia the other
246
+ * rules were sized against, it forbids 561 edges across 144 shapes — 个人 203
247
+ * of them, then 杯水, 天一, 口气, 些小 and 家人 — and moves 53 decodes, since
248
+ * most of what it takes away the cost model was not going to choose anyway.
249
+ * Three of the 53 are wrong: 一批评, 这一名词 and 六七股灾, where the 一 and the
250
+ * 六七 count nothing and no tag says so. Exactly one reading changes over the
251
+ * corpus and it is a fix, 下了两天雨 having read 天雨 as `tiān yù`.
252
+ */
253
+ export const COUNTED_MEASURE = {
254
+ name: "counted-measure",
255
+ verdictFor: (context) => {
256
+ const { characters, edge } = context;
257
+ // A reading of a different length from its span says something about how
258
+ // the characters are read rather than only about where they break: 份儿 is
259
+ // `fènr` over two characters, and taking the edge away would take the 儿化
260
+ // with it.
261
+ if (edge.to - edge.from < 2 ||
262
+ edge.reading.length !== edge.to - edge.from) {
263
+ return "keep";
264
+ }
265
+ const counted = countedFrom(context, edge.from);
266
+ if (counted === undefined) {
267
+ return "keep";
268
+ }
269
+ return tagOf(context, characters[edge.from]) === MEASURE &&
270
+ edge.partOfSpeech !== MEASURE
271
+ ? "forbid"
272
+ : "keep";
273
+ },
274
+ };
103
275
  /**
104
276
  * The rules the lattice decode applies, in order.
105
277
  */
106
- export const READING_RULES = [MODAL_DE, ATTESTED_ERHUA];
278
+ export const READING_RULES = [
279
+ MODAL_DE,
280
+ TEACHING_JIAO,
281
+ ATTESTED_ERHUA,
282
+ COUNTED_MEASURE,
283
+ ];
107
284
  //# sourceMappingURL=reading-rules.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reading-rules.js","sourceRoot":"","sources":["../../src/decode/reading-rules.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,EACL,YAAY,EACZ,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,SAAS,KAAK,CAAC,QAA8B;IAC3C,OAAO,CACL,QAAQ,EAAE,OAAO,KAAK,GAAG,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC,CAC5E,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,aAAa,GAAG,eAAe,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAa;IAChC,IAAI,EAAE,UAAU;IAChB,UAAU,EAAE,CAAC,OAAoB,EAAE,EAAE;QACnC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/C,OAAO,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC7C,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACzC,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,MAAM,CAAC;IACb,CAAC;CACF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAa;IACtC,IAAI,EAAE,gBAAgB;IACtB,UAAU,EAAE,CAAC,OAAoB,EAAE,EAAE;QACnC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;QACrC,IAAI,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACzC,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,wEAAwE;QACxE,MAAM,QAAQ,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC;QACvD,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAC7D,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/D,CAAC;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAwB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC"}
1
+ {"version":3,"file":"reading-rules.js","sourceRoot":"","sources":["../../src/decode/reading-rules.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../../src/dictionary/assertions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,MAAM,GAAG,SAAS,CAAC;CACrE;AAED;;GAEG;AACH,qBAAa,eAAe;;IAG1B,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAE7C;;;;;OAKG;IACH,YAAY,OAAO,EAAE,SAAS,eAAe,EAAE,EAc9C;IAeD;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAE7C;IAED;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKxC;IAED;;OAEG;IACH,iBAAiB,IAAI,WAAW,CAAC,MAAM,CAAC,CAUvC;IAED;;;;;;;;OAQG;IACH,eAAe,IAAI,WAAW,CAAC,MAAM,CAAC,CAUrC;CACF;AAoBD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAgHrD,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,SAAS,eAAe,EAAE,GAClC,SAAS,MAAM,EAAE,CAKnB"}
1
+ {"version":3,"file":"assertions.d.ts","sourceRoot":"","sources":["../../src/dictionary/assertions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,MAAM,GAAG,SAAS,CAAC;CACrE;AAED;;GAEG;AACH,qBAAa,eAAe;;IAG1B,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAE7C;;;;;OAKG;IACH,YAAY,OAAO,EAAE,SAAS,eAAe,EAAE,EAc9C;IAeD;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAE7C;IAED;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKxC;IAED;;OAEG;IACH,iBAAiB,IAAI,WAAW,CAAC,MAAM,CAAC,CAUvC;IAED;;;;;;;;OAQG;IACH,eAAe,IAAI,WAAW,CAAC,MAAM,CAAC,CAUrC;CACF;AAoBD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAwIrD,CAAC;AAEF;;GAEG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,SAAS,eAAe,EAAE,GAClC,SAAS,MAAM,EAAE,CAKnB"}
@@ -170,6 +170,26 @@ export const BUILD_ASSERTIONS = [
170
170
  : `no longer proper nouns: ${wrong.join(", ")}`;
171
171
  },
172
172
  },
173
+ {
174
+ // The gap composeLocaleDeltas closes, and the two ways it must not close
175
+ // it. 運行狀況 contains the marked word 行狀 and cuts elsewhere; 一个个地
176
+ // ends in a character whose delta is a different sense of 地 entirely.
177
+ description: "垃圾分類 inherits 垃圾's 國語 reading, 運行狀況 inherits nothing",
178
+ check: (dictionary) => {
179
+ const taiwan = (word) => dictionary
180
+ .get(word)
181
+ ?.readings.tw?.map((syllable) => writeSyllable(syllable))
182
+ .join(" ");
183
+ const composed = taiwan("垃圾分类");
184
+ if (composed !== "lè sè fēn lèi") {
185
+ return `垃圾分类 reads ${composed ?? "nothing"} in zh-TW, expected lè sè fēn lèi`;
186
+ }
187
+ const wrong = ["运行状况", "一个个地", "相亲相爱"].filter((word) => taiwan(word) !== undefined);
188
+ return wrong.length === 0
189
+ ? undefined
190
+ : `composed a zh-TW reading for: ${wrong.join(", ")}`;
191
+ },
192
+ },
173
193
  {
174
194
  description: "头发 derives 頭髮 rather than 頭發",
175
195
  check: (dictionary) => {
@@ -1 +1 @@
1
- {"version":3,"file":"assertions.js","sourceRoot":"","sources":["../../src/dictionary/assertions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"assertions.js","sourceRoot":"","sources":["../../src/dictionary/assertions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { type DictionaryEntry } from "./entry.js";
2
+ /**
3
+ * Compounds whose constituent's 國語 reading must not be carried over.
4
+ *
5
+ * The composition below only fires where the compound reads its constituent
6
+ * exactly as that constituent's own entry does, which rules out most of the ways
7
+ * it could be wrong. What it cannot rule out is a homograph: two words spelled
8
+ * and read identically in 普通话, of which only one shifts in 國語. 相亲 is
9
+ * `xiāngqīn` either way when it means "mutually close" and `xiàngqīn` in Taiwan
10
+ * when it means a matchmaking meeting, and nothing in the data distinguishes the
11
+ * two senses.
12
+ *
13
+ * **Keep this small**, for the same reason
14
+ * {@link import("./overrides.js").READING_OVERRIDES} is kept small. Three
15
+ * exclusions against the 100 compounds the composition settles is the measured
16
+ * ratio; a list growing much past that would mean the alignment check has
17
+ * stopped carrying its weight and the rule itself needs revisiting.
18
+ */
19
+ export declare const LOCALE_COMPOSITION_EXCLUSIONS: readonly {
20
+ readonly word: string;
21
+ readonly reason: string;
22
+ }[];
23
+ /**
24
+ * What the composition pass changed.
25
+ */
26
+ export interface LocaleComposition {
27
+ readonly entries: readonly DictionaryEntry[];
28
+ /** Compounds given a zh-TW delta they had no source for. */
29
+ readonly composed: number;
30
+ }
31
+ /**
32
+ * Give a compound the 國語 reading its constituents already carry.
33
+ *
34
+ * The gap this closes: `zh-TW` is a delta over `zh-CN`, and a source marks that
35
+ * delta on whichever headword it happened to list. CC-CEDICT marks 垃圾 and
36
+ * 垃圾桶 but not 垃圾分类, and because the decoder prefers the longest word it
37
+ * finds, 垃圾分类 is decoded whole and 垃圾's delta is never consulted — so a
38
+ * locale switch that visibly works on 垃圾 appears to do nothing on 垃圾分类.
39
+ * Patching the entries a source missed would be endless; the compounds are
40
+ * open-class and the marked words are not.
41
+ *
42
+ * Three things keep it from overreaching, and all three are load-bearing:
43
+ *
44
+ * 1. **Segmentation, not substring.** 运行状况 contains 行状, which carries a
45
+ * delta, but cuts as 运行 + 状况 and never as 运 + 行状 + 况.
46
+ * 2. **The constituent must be read here as its own entry reads it**, or the
47
+ * compound means something else by those characters.
48
+ * 3. **Only whole words contribute**, never single characters — see
49
+ * {@link composedReading} for what allowing them costs.
50
+ *
51
+ * What survives all three is a homograph the data cannot see, which is what
52
+ * {@link LOCALE_COMPOSITION_EXCLUSIONS} is for.
53
+ */
54
+ export declare function composeLocaleDeltas(entries: readonly DictionaryEntry[]): LocaleComposition;
55
+ //# sourceMappingURL=locale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../src/dictionary/locale.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,eAAe,EAAiB,MAAM,YAAY,CAAC;AAGjE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,6BAA6B,EAAE,SAAS;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,EAeA,CAAC;AA6PF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAYD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,SAAS,eAAe,EAAE,GAClC,iBAAiB,CA8DnB"}
@@ -0,0 +1,323 @@
1
+ import { isSingleCharacter, toCharacters } from "../script/characters.js";
2
+ import { isSameReading } from "./entry.js";
3
+ import { FrequencyTable } from "./frequency-table.js";
4
+ /**
5
+ * Compounds whose constituent's 國語 reading must not be carried over.
6
+ *
7
+ * The composition below only fires where the compound reads its constituent
8
+ * exactly as that constituent's own entry does, which rules out most of the ways
9
+ * it could be wrong. What it cannot rule out is a homograph: two words spelled
10
+ * and read identically in 普通话, of which only one shifts in 國語. 相亲 is
11
+ * `xiāngqīn` either way when it means "mutually close" and `xiàngqīn` in Taiwan
12
+ * when it means a matchmaking meeting, and nothing in the data distinguishes the
13
+ * two senses.
14
+ *
15
+ * **Keep this small**, for the same reason
16
+ * {@link import("./overrides.js").READING_OVERRIDES} is kept small. Three
17
+ * exclusions against the 100 compounds the composition settles is the measured
18
+ * ratio; a list growing much past that would mean the alignment check has
19
+ * stopped carrying its weight and the rule itself needs revisiting.
20
+ */
21
+ export const LOCALE_COMPOSITION_EXCLUSIONS = [
22
+ {
23
+ word: "相亲相爱",
24
+ reason: "相亲 here is 相 + 亲 in the reciprocal sense, `xiāngqīn`, not the matchmaking word that reads `xiàngqīn` in 國語.",
25
+ },
26
+ {
27
+ word: "腹背相亲",
28
+ reason: "The same reciprocal 相亲, so the same reading in both locales.",
29
+ },
30
+ {
31
+ word: "洋为中用",
32
+ reason: "中 is 中国 and 用 is its own verb: 洋为中用 is not built on the word 中用, which is what reads `zhòngyòng` in 國語.",
33
+ },
34
+ ];
35
+ /**
36
+ * The exclusions as a set, for the composition to consult.
37
+ */
38
+ const EXCLUDED = new Set(LOCALE_COMPOSITION_EXCLUSIONS.map((exclusion) => exclusion.word));
39
+ /**
40
+ * What each word in a segmentation costs on top of its frequency.
41
+ *
42
+ * The same charge, and for the same reason, as `READING_CHARGE` in
43
+ * `src/decode/lattice.ts`: it orders candidate segmentations by fewest words
44
+ * first and frequency second. That ordering is what decides 皮夹克 is 皮 + 夹克
45
+ * rather than 皮夹 + 克, which is the difference between leaving `pí jiákè`
46
+ * alone and mistaking it for the wallet 皮夹 that reads `pí jiá` in 國語.
47
+ *
48
+ * Duplicated rather than imported because the decoder sits above the dictionary
49
+ * and must not be depended on from inside it. The two are kept in step by
50
+ * `src/dictionary/locale.test.ts`, which asserts the segmentations this value
51
+ * produces rather than the value itself.
52
+ */
53
+ const WORD_CHARGE = 16;
54
+ /**
55
+ * Every key an entry answers to, under either script.
56
+ */
57
+ function keysOf(entry) {
58
+ return [entry.hans, entry.hant, ...(entry.hantVariants ?? [])];
59
+ }
60
+ /**
61
+ * A compound's constituent words, and the readings they carry.
62
+ *
63
+ * Indexed by every key an entry claims, so that a 繁體 headword segments into
64
+ * 繁體 constituents. Headwords are set first and never displaced, which is the
65
+ * same precedence the artifact and the build assertions use: 发 and 髮 both claim
66
+ * 发, and the entry the key names has to win.
67
+ */
68
+ class ConstituentIndex {
69
+ #byKey;
70
+ #cost;
71
+ /**
72
+ * Index entries by key, quantising their frequencies the way the decoder will.
73
+ */
74
+ constructor(entries) {
75
+ const byKey = new Map();
76
+ for (const entry of entries) {
77
+ byKey.set(entry.hans, entry);
78
+ }
79
+ for (const entry of entries) {
80
+ for (const form of keysOf(entry)) {
81
+ if (!byKey.has(form)) {
82
+ byKey.set(form, entry);
83
+ }
84
+ }
85
+ }
86
+ // Costs come from the same quantised table the artifact ships, so that a
87
+ // segmentation decided here is the one the decoder would have reached.
88
+ const table = FrequencyTable.build(entries.map((entry) => entry.frequency));
89
+ const byEntry = new Map();
90
+ for (const [at, entry] of entries.entries()) {
91
+ byEntry.set(entry, table.costOf(at));
92
+ }
93
+ const cost = new Map();
94
+ for (const [key, entry] of byKey) {
95
+ /* c8 ignore next -- every key was indexed from one of these entries */
96
+ cost.set(key, (byEntry.get(entry) ?? 0) + WORD_CHARGE);
97
+ }
98
+ this.#byKey = byKey;
99
+ this.#cost = cost;
100
+ }
101
+ /**
102
+ * Relax every word starting at one position, improving the costs it reaches.
103
+ *
104
+ * One step of the shortest-path scan, lifted out of its enclosing loop.
105
+ */
106
+ #relaxFrom(word, characters, from, best, previous) {
107
+ /* c8 ignore next -- the caller only relaxes from a position in range */
108
+ const reached = best[from] ?? Infinity;
109
+ let candidate = "";
110
+ for (let to = from; to < characters.length; to++) {
111
+ /* c8 ignore next -- the loop condition keeps the index in range */
112
+ candidate += characters[to] ?? "";
113
+ // The whole word is not a segmentation of itself.
114
+ const entry = candidate === word ? undefined : this.#byKey.get(candidate);
115
+ const total = reached + this.costOf(candidate);
116
+ /* c8 ignore next -- `to + 1` is at most `length`, which `best` holds */
117
+ if (entry !== undefined && total < (best[to + 1] ?? Infinity)) {
118
+ best[to + 1] = total;
119
+ previous[to + 1] = { entry, text: candidate, from, to: to + 1 };
120
+ }
121
+ }
122
+ }
123
+ /**
124
+ * What a key costs a segmentation that uses it.
125
+ */
126
+ costOf(key) {
127
+ /* c8 ignore next -- only asked about keys the index just returned */
128
+ return this.#cost.get(key) ?? WORD_CHARGE;
129
+ }
130
+ /**
131
+ * The likeliest way to cut a word into *other* dictionary words.
132
+ *
133
+ * The whole word is excluded from its own segmentation, which is the point:
134
+ * 垃圾分类 is a dictionary word, so a decoder handed the text would stop there
135
+ * and never ask what it is made of. This asks anyway.
136
+ *
137
+ * Returns undefined when no path of known words covers the word, which is
138
+ * common — most compounds contain a character no shorter word claims.
139
+ */
140
+ segment(word) {
141
+ const characters = toCharacters(word);
142
+ const length = characters.length;
143
+ const best = Array.from({ length: length + 1 }, () => Infinity);
144
+ const previous = Array.from({ length: length + 1 });
145
+ best[0] = 0;
146
+ for (let from = 0; from < length; from++) {
147
+ /* c8 ignore next -- the loop condition keeps the index in range */
148
+ if ((best[from] ?? Infinity) !== Infinity) {
149
+ this.#relaxFrom(word, characters, from, best, previous);
150
+ }
151
+ }
152
+ /* c8 ignore next -- `best` is built one longer than the word */
153
+ if ((best[length] ?? Infinity) === Infinity) {
154
+ return undefined;
155
+ }
156
+ const path = [];
157
+ let at = length;
158
+ while (at > 0) {
159
+ const step = previous[at];
160
+ /* c8 ignore next 3 -- a finite cost at `at` means a step reached it */
161
+ if (step === undefined) {
162
+ return undefined;
163
+ }
164
+ path.unshift(step);
165
+ at = step.from;
166
+ }
167
+ return path;
168
+ }
169
+ }
170
+ /**
171
+ * The 國語 reading a compound inherits from its constituents, or undefined.
172
+ *
173
+ * Only a **multi-character** constituent may contribute. A character's delta
174
+ * reaches every compound that character appears in, and surviving the merge's
175
+ * sense test — see `isOwnSense` — does not make one locale-wide. 期 carries
176
+ * `qí`, which 國語 really does read everywhere, but 會 carries `huǐ`, and
177
+ * propagating it would rewrite 三合会 as `sānhéhuǐ`. Measured on the full
178
+ * dictionary, allowing characters to contribute composes 3,743 entries against
179
+ * the 101 that multi-character words compose.
180
+ */
181
+ function composedReading(entry, path, settled) {
182
+ const reading = entry.readings.cn;
183
+ const composed = [];
184
+ let isContributed = false;
185
+ for (const part of path) {
186
+ const span = reading.slice(part.from, part.to);
187
+ // A constituent an earlier round composed counts as carrying a delta, which
188
+ // is what lets 中看不中用 inherit through 不中用.
189
+ const taiwan = settled.get(part.entry.hans) ?? part.entry.readings.tw;
190
+ if (taiwan === undefined || isSingleCharacter(part.text)) {
191
+ composed.push(...span);
192
+ continue;
193
+ }
194
+ // The compound has to read this constituent the way the constituent's own
195
+ // entry reads it. Where it does not, the compound is using another sense —
196
+ // 浑身解数 is `xiè shù` and 解数 is a word, but a compound reading `jiě shù`
197
+ // there would be a different word wearing the same characters, and its
198
+ // 國語 reading says nothing about this one.
199
+ if (!isSameReading(part.entry.readings.cn, span)) {
200
+ return undefined;
201
+ }
202
+ composed.push(...taiwan);
203
+ isContributed = true;
204
+ }
205
+ return isContributed ? composed : undefined;
206
+ }
207
+ /**
208
+ * Whether a compound can be asked what it is made of at all.
209
+ *
210
+ * A reading of one syllable per character is what lets a character span be cut
211
+ * out of the compound's reading and replaced. 儿化 folds two characters into one
212
+ * syllable and punctuation reads as none, so neither lines up and both are left
213
+ * alone.
214
+ */
215
+ function isComposable(entry) {
216
+ const characters = toCharacters(entry.hans);
217
+ return (characters.length > 2 &&
218
+ entry.readings.cn.length === characters.length &&
219
+ !EXCLUDED.has(entry.hans) &&
220
+ !EXCLUDED.has(entry.hant));
221
+ }
222
+ /**
223
+ * Whether any delta-carrying word appears inside this one.
224
+ *
225
+ * A cheap filter ahead of the segmentation: only a few hundred words carry a
226
+ * delta, against 461,623 entries, and enumerating one word's substrings costs
227
+ * far less than segmenting it.
228
+ */
229
+ function hasMarkedConstituent(word, marked) {
230
+ const characters = toCharacters(word);
231
+ return characters.some((_, from) => {
232
+ let candidate = "";
233
+ return characters.slice(from).some((character) => {
234
+ candidate += character;
235
+ return candidate !== word && marked.has(candidate);
236
+ });
237
+ });
238
+ }
239
+ /**
240
+ * How many times the pass may feed its own output back in.
241
+ *
242
+ * 中看不中用 needs two rounds: 不中用 is itself a compound with no delta of its
243
+ * own, so only once it has been composed can the phrase containing it inherit
244
+ * one. Nothing measured needs a third, and the bound is what stops a cycle in
245
+ * the data from turning into a loop here.
246
+ */
247
+ const ROUNDS = 4;
248
+ /**
249
+ * Give a compound the 國語 reading its constituents already carry.
250
+ *
251
+ * The gap this closes: `zh-TW` is a delta over `zh-CN`, and a source marks that
252
+ * delta on whichever headword it happened to list. CC-CEDICT marks 垃圾 and
253
+ * 垃圾桶 but not 垃圾分类, and because the decoder prefers the longest word it
254
+ * finds, 垃圾分类 is decoded whole and 垃圾's delta is never consulted — so a
255
+ * locale switch that visibly works on 垃圾 appears to do nothing on 垃圾分类.
256
+ * Patching the entries a source missed would be endless; the compounds are
257
+ * open-class and the marked words are not.
258
+ *
259
+ * Three things keep it from overreaching, and all three are load-bearing:
260
+ *
261
+ * 1. **Segmentation, not substring.** 运行状况 contains 行状, which carries a
262
+ * delta, but cuts as 运行 + 状况 and never as 运 + 行状 + 况.
263
+ * 2. **The constituent must be read here as its own entry reads it**, or the
264
+ * compound means something else by those characters.
265
+ * 3. **Only whole words contribute**, never single characters — see
266
+ * {@link composedReading} for what allowing them costs.
267
+ *
268
+ * What survives all three is a homograph the data cannot see, which is what
269
+ * {@link LOCALE_COMPOSITION_EXCLUSIONS} is for.
270
+ */
271
+ export function composeLocaleDeltas(entries) {
272
+ const index = new ConstituentIndex(entries);
273
+ // Only a compound containing a delta-carrying word can gain one, and there
274
+ // are a few hundred such words against 461,623 entries. Enumerating each
275
+ // entry's substrings against that set costs far less than segmenting every
276
+ // entry would.
277
+ const marked = new Set();
278
+ for (const entry of entries) {
279
+ if (entry.readings.tw !== undefined && !isSingleCharacter(entry.hans)) {
280
+ for (const key of keysOf(entry)) {
281
+ marked.add(key);
282
+ }
283
+ }
284
+ }
285
+ // Candidates are fixed after the first sweep: composing a delta never makes a
286
+ // new compound eligible, only changes what an eligible one inherits.
287
+ const candidates = entries.filter((entry) => entry.readings.tw === undefined &&
288
+ isComposable(entry) &&
289
+ hasMarkedConstituent(entry.hans, marked));
290
+ const settled = new Map();
291
+ /**
292
+ * Settle one compound, reporting whether that changed what it reads.
293
+ */
294
+ const didCompose = (entry) => {
295
+ const path = index.segment(entry.hans);
296
+ const reading = path === undefined ? undefined : composedReading(entry, path, settled);
297
+ // A composition that comes back identical to the 普通话 reading is not a
298
+ // delta at all, and one identical to the last round's is not news.
299
+ if (reading === undefined || isSameReading(reading, entry.readings.cn)) {
300
+ return false;
301
+ }
302
+ const held = settled.get(entry.hans);
303
+ if (held !== undefined && isSameReading(held, reading)) {
304
+ return false;
305
+ }
306
+ settled.set(entry.hans, reading);
307
+ return true;
308
+ };
309
+ for (let round = 0; round < ROUNDS; round++) {
310
+ const changed = candidates.filter((entry) => didCompose(entry)).length;
311
+ if (changed === 0) {
312
+ break;
313
+ }
314
+ }
315
+ const composed = entries.map((entry) => {
316
+ const reading = settled.get(entry.hans);
317
+ return reading === undefined
318
+ ? entry
319
+ : { ...entry, readings: { ...entry.readings, tw: reading } };
320
+ });
321
+ return { entries: composed, composed: settled.size };
322
+ }
323
+ //# sourceMappingURL=locale.js.map