tdk-api-wrapper 1.3.1 → 1.4.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/README.md +13 -5
- package/dist/{chunk-ACMGCL7T.mjs → chunk-5TYJDVHK.mjs} +620 -6
- package/dist/cli.js +653 -9
- package/dist/cli.mjs +41 -4
- package/dist/index.d.mts +88 -1
- package/dist/index.d.ts +88 -1
- package/dist/index.js +629 -8
- package/dist/index.mjs +17 -3
- package/package.json +3 -2
- package/src/cli.ts +40 -3
- package/src/index.ts +1 -0
- package/src/morphology.ts +214 -0
- package/src/tdk.ts +135 -6
- package/src/types.ts +11 -0
- package/test/morphology.test.js +105 -0
|
@@ -25,6 +25,503 @@ var TDKNetworkError = class extends TDKError {
|
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
// src/morphology.ts
|
|
29
|
+
var TURKISH_VOWELS = "ae\u0131io\xF6u\xFC";
|
|
30
|
+
function isVowel(ch) {
|
|
31
|
+
return TURKISH_VOWELS.includes(ch);
|
|
32
|
+
}
|
|
33
|
+
var TURKISH_SUFFIXES = [
|
|
34
|
+
// 9-letter composite suffixes
|
|
35
|
+
"lerimizden",
|
|
36
|
+
"lar\u0131m\u0131zdan",
|
|
37
|
+
"lerinizden",
|
|
38
|
+
"lar\u0131n\u0131zdan",
|
|
39
|
+
// 8-letter composite suffixes
|
|
40
|
+
"lerinin",
|
|
41
|
+
"lar\u0131n\u0131n",
|
|
42
|
+
"lerinde",
|
|
43
|
+
"lar\u0131nda",
|
|
44
|
+
"lerinden",
|
|
45
|
+
"lar\u0131ndan",
|
|
46
|
+
"leriyle",
|
|
47
|
+
"lar\u0131yla",
|
|
48
|
+
"lerini",
|
|
49
|
+
"lar\u0131n\u0131",
|
|
50
|
+
"lerimize",
|
|
51
|
+
"lar\u0131m\u0131za",
|
|
52
|
+
"lerimizle",
|
|
53
|
+
"lar\u0131m\u0131zla",
|
|
54
|
+
"lerinizin",
|
|
55
|
+
"lar\u0131n\u0131z\u0131n",
|
|
56
|
+
"lerinizde",
|
|
57
|
+
"lar\u0131n\u0131zda",
|
|
58
|
+
"d\u0131\u011F\u0131ndan",
|
|
59
|
+
"di\u011Finden",
|
|
60
|
+
"du\u011Fundan",
|
|
61
|
+
"d\xFC\u011F\xFCnden",
|
|
62
|
+
"t\u0131\u011F\u0131ndan",
|
|
63
|
+
"ti\u011Finden",
|
|
64
|
+
"tu\u011Fundan",
|
|
65
|
+
"t\xFC\u011F\xFCnden",
|
|
66
|
+
// 7-letter composite suffixes
|
|
67
|
+
"ecektir",
|
|
68
|
+
"acakt\u0131r",
|
|
69
|
+
"ece\u011Fim",
|
|
70
|
+
"aca\u011F\u0131m",
|
|
71
|
+
"eceksin",
|
|
72
|
+
"acaks\u0131n",
|
|
73
|
+
"ece\u011Fiz",
|
|
74
|
+
"aca\u011F\u0131z",
|
|
75
|
+
"lerimiz",
|
|
76
|
+
"lar\u0131m\u0131z",
|
|
77
|
+
"leriniz",
|
|
78
|
+
"lar\u0131n\u0131z",
|
|
79
|
+
"umuzdan",
|
|
80
|
+
"\xFCm\xFCzden",
|
|
81
|
+
"inizden",
|
|
82
|
+
"\u0131n\u0131zdan",
|
|
83
|
+
"\xFCn\xFCzden",
|
|
84
|
+
"d\u0131\u011F\u0131nda",
|
|
85
|
+
"di\u011Finde",
|
|
86
|
+
"du\u011Funda",
|
|
87
|
+
"d\xFC\u011F\xFCnde",
|
|
88
|
+
"t\u0131\u011F\u0131nda",
|
|
89
|
+
"ti\u011Finde",
|
|
90
|
+
"tu\u011Funda",
|
|
91
|
+
"t\xFC\u011F\xFCnde",
|
|
92
|
+
"mas\u0131na",
|
|
93
|
+
"mesine",
|
|
94
|
+
"\u0131yorsunuz",
|
|
95
|
+
"iyorsunuz",
|
|
96
|
+
"uyorsunuz",
|
|
97
|
+
"\xFCyorsunuz",
|
|
98
|
+
"yorsunuz",
|
|
99
|
+
// 6-letter composite suffixes
|
|
100
|
+
"iyorsa",
|
|
101
|
+
"iyorduk",
|
|
102
|
+
"iyordu",
|
|
103
|
+
"iyormu\u015F",
|
|
104
|
+
"\u0131yorsa",
|
|
105
|
+
"\u0131yorduk",
|
|
106
|
+
"\u0131yordu",
|
|
107
|
+
"\u0131yormu\u015F",
|
|
108
|
+
"uyorsa",
|
|
109
|
+
"uyorduk",
|
|
110
|
+
"uyordu",
|
|
111
|
+
"uyormu\u015F",
|
|
112
|
+
"\xFCyorsa",
|
|
113
|
+
"\xFCyorduk",
|
|
114
|
+
"\xFCyordu",
|
|
115
|
+
"\xFCyormu\u015F",
|
|
116
|
+
"\u0131yorsun",
|
|
117
|
+
"iyorsun",
|
|
118
|
+
"uyorsun",
|
|
119
|
+
"\xFCyorsun",
|
|
120
|
+
"\u0131yorlar",
|
|
121
|
+
"iyorlar",
|
|
122
|
+
"uyorlar",
|
|
123
|
+
"\xFCyorlar",
|
|
124
|
+
"iyoruz",
|
|
125
|
+
"\u0131yoruz",
|
|
126
|
+
"uyoruz",
|
|
127
|
+
"\xFCyoruz",
|
|
128
|
+
"imizin",
|
|
129
|
+
"\u0131m\u0131z\u0131n",
|
|
130
|
+
"umuzun",
|
|
131
|
+
"\xFCm\xFCz\xFCn",
|
|
132
|
+
"imizde",
|
|
133
|
+
"\u0131m\u0131zda",
|
|
134
|
+
"umuzda",
|
|
135
|
+
"\xFCm\xFCzde",
|
|
136
|
+
"imizden",
|
|
137
|
+
"\u0131m\u0131zdan",
|
|
138
|
+
"imizle",
|
|
139
|
+
"\u0131m\u0131zla",
|
|
140
|
+
"umuzla",
|
|
141
|
+
"\xFCm\xFCzle",
|
|
142
|
+
"lerdir",
|
|
143
|
+
"lard\u0131r",
|
|
144
|
+
"mu\u015Ftur",
|
|
145
|
+
"mi\u015Ftir",
|
|
146
|
+
"mu\u015Ftur",
|
|
147
|
+
"m\xFC\u015Ft\xFCr",
|
|
148
|
+
"lerden",
|
|
149
|
+
"lardan",
|
|
150
|
+
"lerine",
|
|
151
|
+
"lar\u0131na",
|
|
152
|
+
"leriyle",
|
|
153
|
+
"lar\u0131yla",
|
|
154
|
+
"seniz",
|
|
155
|
+
"san\u0131z",
|
|
156
|
+
"diniz",
|
|
157
|
+
"d\u0131n\u0131z",
|
|
158
|
+
"dunuz",
|
|
159
|
+
"d\xFCn\xFCz",
|
|
160
|
+
"tiniz",
|
|
161
|
+
"t\u0131n\u0131z",
|
|
162
|
+
"tunuz",
|
|
163
|
+
"t\xFCn\xFCz",
|
|
164
|
+
"siniz",
|
|
165
|
+
"s\u0131n\u0131z",
|
|
166
|
+
"sunuz",
|
|
167
|
+
"s\xFCn\xFCz",
|
|
168
|
+
"d\u0131k\xE7a",
|
|
169
|
+
"dik\xE7e",
|
|
170
|
+
"duk\xE7a",
|
|
171
|
+
"d\xFCk\xE7e",
|
|
172
|
+
"t\u0131k\xE7a",
|
|
173
|
+
"tik\xE7e",
|
|
174
|
+
"tuk\xE7a",
|
|
175
|
+
"t\xFCk\xE7e",
|
|
176
|
+
"\u0131rken",
|
|
177
|
+
"irken",
|
|
178
|
+
"urken",
|
|
179
|
+
"\xFCrken",
|
|
180
|
+
"arken",
|
|
181
|
+
"erken",
|
|
182
|
+
// 5-letter suffixes
|
|
183
|
+
"lerde",
|
|
184
|
+
"larda",
|
|
185
|
+
"lerle",
|
|
186
|
+
"larla",
|
|
187
|
+
"lerin",
|
|
188
|
+
"lar\u0131n",
|
|
189
|
+
"lerim",
|
|
190
|
+
"lar\u0131m",
|
|
191
|
+
"dirler",
|
|
192
|
+
"d\u0131rlar",
|
|
193
|
+
"d\xFCrler",
|
|
194
|
+
"durlar",
|
|
195
|
+
"tirler",
|
|
196
|
+
"t\u0131rlar",
|
|
197
|
+
"t\xFCrler",
|
|
198
|
+
"turlar",
|
|
199
|
+
"siniz",
|
|
200
|
+
"s\u0131n\u0131z",
|
|
201
|
+
"sunuz",
|
|
202
|
+
"s\xFCn\xFCz",
|
|
203
|
+
"yorum",
|
|
204
|
+
"yorsun",
|
|
205
|
+
"uyoruz",
|
|
206
|
+
"yorsunuz",
|
|
207
|
+
"yorlar",
|
|
208
|
+
"eceks",
|
|
209
|
+
"acaks",
|
|
210
|
+
"eyim",
|
|
211
|
+
"ay\u0131m",
|
|
212
|
+
"indik",
|
|
213
|
+
"\u0131nd\u0131k",
|
|
214
|
+
"unduk",
|
|
215
|
+
"\xFCnd\xFCk",
|
|
216
|
+
"ildik",
|
|
217
|
+
"\u0131ld\u0131k",
|
|
218
|
+
"ulduk",
|
|
219
|
+
"\xFCld\xFCk",
|
|
220
|
+
"meden",
|
|
221
|
+
"madan",
|
|
222
|
+
"y\u0131n\u0131z",
|
|
223
|
+
"yiniz",
|
|
224
|
+
"yunuz",
|
|
225
|
+
"y\xFCn\xFCz",
|
|
226
|
+
// 4-letter suffixes
|
|
227
|
+
"imiz",
|
|
228
|
+
"\u0131m\u0131z",
|
|
229
|
+
"umuz",
|
|
230
|
+
"\xFCm\xFCz",
|
|
231
|
+
"iniz",
|
|
232
|
+
"\u0131n\u0131z",
|
|
233
|
+
"unuz",
|
|
234
|
+
"\xFCn\xFCz",
|
|
235
|
+
"leri",
|
|
236
|
+
"lar\u0131",
|
|
237
|
+
"idir",
|
|
238
|
+
"\u0131d\u0131r",
|
|
239
|
+
"udur",
|
|
240
|
+
"\xFCd\xFCr",
|
|
241
|
+
"ecek",
|
|
242
|
+
"acak",
|
|
243
|
+
"erek",
|
|
244
|
+
"arak",
|
|
245
|
+
"ince",
|
|
246
|
+
"\u0131nca",
|
|
247
|
+
"unca",
|
|
248
|
+
"\xFCnce",
|
|
249
|
+
"ken",
|
|
250
|
+
"meli",
|
|
251
|
+
"mal\u0131",
|
|
252
|
+
"iyor",
|
|
253
|
+
"\u0131yor",
|
|
254
|
+
"uyor",
|
|
255
|
+
"\xFCyor",
|
|
256
|
+
"mi\u015Fti",
|
|
257
|
+
"m\u0131\u015Ft\u0131",
|
|
258
|
+
"mu\u015Ftu",
|
|
259
|
+
"m\xFC\u015Ft\xFC",
|
|
260
|
+
"seydi",
|
|
261
|
+
"sayd\u0131",
|
|
262
|
+
"ydim",
|
|
263
|
+
"yd\u0131m",
|
|
264
|
+
"ydum",
|
|
265
|
+
"yd\xFCm",
|
|
266
|
+
"tiler",
|
|
267
|
+
"t\u0131lar",
|
|
268
|
+
"diler",
|
|
269
|
+
"d\u0131lar",
|
|
270
|
+
"ikten",
|
|
271
|
+
"\u0131ktan",
|
|
272
|
+
"uktan",
|
|
273
|
+
"\xFCkten",
|
|
274
|
+
// 3-letter suffixes
|
|
275
|
+
"ler",
|
|
276
|
+
"lar",
|
|
277
|
+
"den",
|
|
278
|
+
"dan",
|
|
279
|
+
"ten",
|
|
280
|
+
"tan",
|
|
281
|
+
"dir",
|
|
282
|
+
"d\u0131r",
|
|
283
|
+
"dur",
|
|
284
|
+
"d\xFCr",
|
|
285
|
+
"tir",
|
|
286
|
+
"t\u0131r",
|
|
287
|
+
"tur",
|
|
288
|
+
"t\xFCr",
|
|
289
|
+
"nin",
|
|
290
|
+
"n\u0131n",
|
|
291
|
+
"nun",
|
|
292
|
+
"n\xFCn",
|
|
293
|
+
"yle",
|
|
294
|
+
"yla",
|
|
295
|
+
"mi\u015F",
|
|
296
|
+
"m\u0131\u015F",
|
|
297
|
+
"mu\u015F",
|
|
298
|
+
"m\xFC\u015F",
|
|
299
|
+
"dim",
|
|
300
|
+
"d\u0131m",
|
|
301
|
+
"dum",
|
|
302
|
+
"d\xFCm",
|
|
303
|
+
"tim",
|
|
304
|
+
"t\u0131m",
|
|
305
|
+
"tum",
|
|
306
|
+
"t\xFCm",
|
|
307
|
+
"din",
|
|
308
|
+
"d\u0131n",
|
|
309
|
+
"dun",
|
|
310
|
+
"d\xFCn",
|
|
311
|
+
"tin",
|
|
312
|
+
"t\u0131n",
|
|
313
|
+
"tun",
|
|
314
|
+
"t\xFCn",
|
|
315
|
+
"dik",
|
|
316
|
+
"d\u0131k",
|
|
317
|
+
"duk",
|
|
318
|
+
"d\xFCk",
|
|
319
|
+
"tik",
|
|
320
|
+
"t\u0131k",
|
|
321
|
+
"tuk",
|
|
322
|
+
"t\xFCk",
|
|
323
|
+
"ydi",
|
|
324
|
+
"yd\u0131",
|
|
325
|
+
"ydu",
|
|
326
|
+
"yd\xFC",
|
|
327
|
+
"yim",
|
|
328
|
+
"y\u0131m",
|
|
329
|
+
"yum",
|
|
330
|
+
"y\xFCm",
|
|
331
|
+
"sin",
|
|
332
|
+
"s\u0131n",
|
|
333
|
+
"sun",
|
|
334
|
+
"s\xFCn",
|
|
335
|
+
"siz",
|
|
336
|
+
"s\u0131z",
|
|
337
|
+
"suz",
|
|
338
|
+
"s\xFCz",
|
|
339
|
+
"lik",
|
|
340
|
+
"l\u0131k",
|
|
341
|
+
"luk",
|
|
342
|
+
"l\xFCk",
|
|
343
|
+
"ici",
|
|
344
|
+
"\u0131c\u0131",
|
|
345
|
+
"ucu",
|
|
346
|
+
"\xFCc\xFC",
|
|
347
|
+
"gen",
|
|
348
|
+
"gan",
|
|
349
|
+
"ken",
|
|
350
|
+
"kan",
|
|
351
|
+
"len",
|
|
352
|
+
"lan",
|
|
353
|
+
"le\u015F",
|
|
354
|
+
"la\u015F",
|
|
355
|
+
"mek",
|
|
356
|
+
"mak",
|
|
357
|
+
"yor",
|
|
358
|
+
// 2-letter suffixes
|
|
359
|
+
"de",
|
|
360
|
+
"da",
|
|
361
|
+
"te",
|
|
362
|
+
"ta",
|
|
363
|
+
"im",
|
|
364
|
+
"\u0131m",
|
|
365
|
+
"um",
|
|
366
|
+
"\xFCm",
|
|
367
|
+
"in",
|
|
368
|
+
"\u0131n",
|
|
369
|
+
"un",
|
|
370
|
+
"\xFCn",
|
|
371
|
+
"iz",
|
|
372
|
+
"\u0131z",
|
|
373
|
+
"uz",
|
|
374
|
+
"\xFCz",
|
|
375
|
+
"si",
|
|
376
|
+
"s\u0131",
|
|
377
|
+
"su",
|
|
378
|
+
"s\xFC",
|
|
379
|
+
"ye",
|
|
380
|
+
"ya",
|
|
381
|
+
"le",
|
|
382
|
+
"la",
|
|
383
|
+
"di",
|
|
384
|
+
"d\u0131",
|
|
385
|
+
"du",
|
|
386
|
+
"d\xFC",
|
|
387
|
+
"ti",
|
|
388
|
+
"t\u0131",
|
|
389
|
+
"tu",
|
|
390
|
+
"t\xFC",
|
|
391
|
+
"se",
|
|
392
|
+
"sa",
|
|
393
|
+
"ce",
|
|
394
|
+
"ca",
|
|
395
|
+
"\xE7e",
|
|
396
|
+
"\xE7a",
|
|
397
|
+
"me",
|
|
398
|
+
"ma",
|
|
399
|
+
"ip",
|
|
400
|
+
"\u0131p",
|
|
401
|
+
"up",
|
|
402
|
+
"\xFCp",
|
|
403
|
+
"en",
|
|
404
|
+
"an",
|
|
405
|
+
"i\u015F",
|
|
406
|
+
"\u0131\u015F",
|
|
407
|
+
"u\u015F",
|
|
408
|
+
"\xFC\u015F",
|
|
409
|
+
"li",
|
|
410
|
+
"l\u0131",
|
|
411
|
+
"lu",
|
|
412
|
+
"l\xFC",
|
|
413
|
+
"ci",
|
|
414
|
+
"c\u0131",
|
|
415
|
+
"cu",
|
|
416
|
+
"c\xFC",
|
|
417
|
+
"\xE7i",
|
|
418
|
+
"\xE7\u0131",
|
|
419
|
+
"\xE7u",
|
|
420
|
+
"\xE7\xFC",
|
|
421
|
+
// 1-letter suffixes (vowels / basic case endings)
|
|
422
|
+
"e",
|
|
423
|
+
"a",
|
|
424
|
+
"i",
|
|
425
|
+
"\u0131",
|
|
426
|
+
"u",
|
|
427
|
+
"\xFC"
|
|
428
|
+
];
|
|
429
|
+
function restoreConsonantSoftening(stem) {
|
|
430
|
+
if (stem.length < 2)
|
|
431
|
+
return [];
|
|
432
|
+
const last = stem.slice(-1);
|
|
433
|
+
const base = stem.slice(0, -1);
|
|
434
|
+
switch (last) {
|
|
435
|
+
case "b":
|
|
436
|
+
return [base + "p"];
|
|
437
|
+
case "c":
|
|
438
|
+
return [base + "\xE7"];
|
|
439
|
+
case "d":
|
|
440
|
+
return [base + "t"];
|
|
441
|
+
case "\u011F":
|
|
442
|
+
return [base + "k"];
|
|
443
|
+
case "g":
|
|
444
|
+
return [base + "k"];
|
|
445
|
+
default:
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function restoreVowelDrop(stem) {
|
|
450
|
+
if (stem.length < 3)
|
|
451
|
+
return [];
|
|
452
|
+
const c1 = stem[stem.length - 2];
|
|
453
|
+
const c2 = stem[stem.length - 1];
|
|
454
|
+
if (!isVowel(c1) && !isVowel(c2)) {
|
|
455
|
+
const vowelsInBase = stem.slice(0, -2).split("").filter(isVowel);
|
|
456
|
+
if (vowelsInBase.length > 0) {
|
|
457
|
+
const lastVowel = vowelsInBase[vowelsInBase.length - 1];
|
|
458
|
+
let inserted = "i";
|
|
459
|
+
if ("a\u0131".includes(lastVowel))
|
|
460
|
+
inserted = "\u0131";
|
|
461
|
+
else if ("ei".includes(lastVowel))
|
|
462
|
+
inserted = "i";
|
|
463
|
+
else if ("ou".includes(lastVowel))
|
|
464
|
+
inserted = "u";
|
|
465
|
+
else if ("\xF6\xFC".includes(lastVowel))
|
|
466
|
+
inserted = "\xFC";
|
|
467
|
+
return [stem.slice(0, -1) + inserted + c2];
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return [];
|
|
471
|
+
}
|
|
472
|
+
function restoreInfinitive(stem) {
|
|
473
|
+
if (stem.length < 2)
|
|
474
|
+
return [];
|
|
475
|
+
const vowelsInBase = stem.split("").filter(isVowel);
|
|
476
|
+
if (vowelsInBase.length === 0)
|
|
477
|
+
return [];
|
|
478
|
+
const lastVowel = vowelsInBase[vowelsInBase.length - 1];
|
|
479
|
+
return "a\u0131ou".includes(lastVowel) ? [stem + "mak"] : [stem + "mek"];
|
|
480
|
+
}
|
|
481
|
+
function getStemCandidates(word, minStemLength = 2, maxDepth = 4) {
|
|
482
|
+
if (!word || word.trim().length === 0)
|
|
483
|
+
return [];
|
|
484
|
+
const raw = word.trim();
|
|
485
|
+
const normalized = raw.toLocaleLowerCase("tr-TR");
|
|
486
|
+
const candidatesWithWeight = [];
|
|
487
|
+
const seen = /* @__PURE__ */ new Set();
|
|
488
|
+
if (raw.includes("'") || raw.includes("\u2019")) {
|
|
489
|
+
const apostropheStem = normalized.split(/['’]/)[0];
|
|
490
|
+
if (apostropheStem.length >= minStemLength) {
|
|
491
|
+
candidatesWithWeight.push({ candidate: apostropheStem, baseLength: apostropheStem.length + 10 });
|
|
492
|
+
seen.add(apostropheStem);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
let frontier = [normalized];
|
|
496
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
497
|
+
const nextFrontier = [];
|
|
498
|
+
for (const current of frontier) {
|
|
499
|
+
for (const suffix of TURKISH_SUFFIXES) {
|
|
500
|
+
if (current.length - suffix.length >= minStemLength && current.endsWith(suffix)) {
|
|
501
|
+
const stem = current.slice(0, -suffix.length);
|
|
502
|
+
const hardened = restoreConsonantSoftening(stem);
|
|
503
|
+
const vowelDropped = restoreVowelDrop(stem);
|
|
504
|
+
const verbalBases = [stem, ...hardened];
|
|
505
|
+
const infinitives = verbalBases.flatMap((v) => restoreInfinitive(v));
|
|
506
|
+
const variants = [stem, ...hardened, ...vowelDropped, ...infinitives];
|
|
507
|
+
for (const variant of variants) {
|
|
508
|
+
if (!seen.has(variant) && variant !== normalized) {
|
|
509
|
+
seen.add(variant);
|
|
510
|
+
nextFrontier.push(variant);
|
|
511
|
+
candidatesWithWeight.push({ candidate: variant, baseLength: stem.length });
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (nextFrontier.length === 0)
|
|
518
|
+
break;
|
|
519
|
+
frontier = nextFrontier;
|
|
520
|
+
}
|
|
521
|
+
candidatesWithWeight.sort((a, b) => b.baseLength - a.baseLength);
|
|
522
|
+
return [...new Set(candidatesWithWeight.map((c) => c.candidate))];
|
|
523
|
+
}
|
|
524
|
+
|
|
28
525
|
// src/tdk.ts
|
|
29
526
|
import * as fs from "fs";
|
|
30
527
|
import * as path from "path";
|
|
@@ -116,6 +613,8 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
116
613
|
static wordCache = /* @__PURE__ */ new Map();
|
|
117
614
|
static dailyContentCache = null;
|
|
118
615
|
static autocompleteCache = [];
|
|
616
|
+
static autocompleteSet = /* @__PURE__ */ new Set();
|
|
617
|
+
static stemCache = /* @__PURE__ */ new Map();
|
|
119
618
|
/**
|
|
120
619
|
* Enables or disables in-memory caching for API requests.
|
|
121
620
|
*/
|
|
@@ -132,6 +631,8 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
132
631
|
this.wordCache.clear();
|
|
133
632
|
this.dailyContentCache = null;
|
|
134
633
|
this.autocompleteCache = [];
|
|
634
|
+
this.autocompleteSet.clear();
|
|
635
|
+
this.stemCache.clear();
|
|
135
636
|
}
|
|
136
637
|
static delay(ms) {
|
|
137
638
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -238,6 +739,17 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
238
739
|
return [];
|
|
239
740
|
}
|
|
240
741
|
}
|
|
742
|
+
/**
|
|
743
|
+
* Ensures TDK's ~81k headword list is loaded in memory for fast O(1) set operations.
|
|
744
|
+
*/
|
|
745
|
+
static async ensureAutocompleteLoaded() {
|
|
746
|
+
if (this.autocompleteCache.length === 0) {
|
|
747
|
+
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
748
|
+
this.autocompleteSet = new Set(
|
|
749
|
+
this.autocompleteCache.map((w) => w.toLocaleLowerCase("tr-TR"))
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
241
753
|
/**
|
|
242
754
|
* Returns autocomplete suggestions for a given prefix, searched over TDK's
|
|
243
755
|
* full headword list (see `fetchAutocompleteData`). The list is fetched
|
|
@@ -247,12 +759,81 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
247
759
|
static async getSuggestions(prefix) {
|
|
248
760
|
if (!prefix || prefix.trim() === "")
|
|
249
761
|
return [];
|
|
250
|
-
|
|
251
|
-
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
252
|
-
}
|
|
762
|
+
await this.ensureAutocompleteLoaded();
|
|
253
763
|
const cleanPrefix = prefix.trim().toLocaleLowerCase("tr-TR");
|
|
254
764
|
return this.autocompleteCache.filter((w) => w.toLocaleLowerCase("tr-TR").startsWith(cleanPrefix)).slice(0, 10);
|
|
255
765
|
}
|
|
766
|
+
/**
|
|
767
|
+
* Checks whether a word exists as a known headword in TDK dictionary.
|
|
768
|
+
* Checks in-memory autocompleteSet (81k headwords) if loaded, or queries TDK API.
|
|
769
|
+
*/
|
|
770
|
+
static async isHeadword(word) {
|
|
771
|
+
if (!word || word.trim() === "")
|
|
772
|
+
return false;
|
|
773
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR");
|
|
774
|
+
await this.ensureAutocompleteLoaded();
|
|
775
|
+
if (this.autocompleteSet.size > 0) {
|
|
776
|
+
return this.autocompleteSet.has(clean);
|
|
777
|
+
}
|
|
778
|
+
try {
|
|
779
|
+
const results = await this.getWord(clean);
|
|
780
|
+
return results.length > 0;
|
|
781
|
+
} catch {
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Generates candidate roots for a given Turkish word using progressive BFS suffix stripping,
|
|
787
|
+
* consonant mutation restoration, and vowel drop restoration.
|
|
788
|
+
*/
|
|
789
|
+
static getStemCandidates(word) {
|
|
790
|
+
return getStemCandidates(word);
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Finds the dictionary root (headword) of a word by checking direct existence
|
|
794
|
+
* and evaluating candidate stems generated by morphological analysis.
|
|
795
|
+
* Returns the root headword string if found, or null if no match in TDK.
|
|
796
|
+
*/
|
|
797
|
+
static async findRoot(word) {
|
|
798
|
+
if (!word || word.trim() === "")
|
|
799
|
+
return null;
|
|
800
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR");
|
|
801
|
+
if (this.stemCache.has(clean)) {
|
|
802
|
+
return this.stemCache.get(clean);
|
|
803
|
+
}
|
|
804
|
+
if (await this.isHeadword(clean)) {
|
|
805
|
+
this.stemCache.set(clean, clean);
|
|
806
|
+
return clean;
|
|
807
|
+
}
|
|
808
|
+
const candidates = getStemCandidates(clean);
|
|
809
|
+
for (const candidate of candidates) {
|
|
810
|
+
if (await this.isHeadword(candidate)) {
|
|
811
|
+
this.stemCache.set(clean, candidate);
|
|
812
|
+
return candidate;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
this.stemCache.set(clean, null);
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Performs morphological stemming on a Turkish word.
|
|
820
|
+
* Returns a StemResult containing the original word, resolved root, and whether it is inflected.
|
|
821
|
+
*/
|
|
822
|
+
static async stem(word) {
|
|
823
|
+
if (!word || word.trim() === "")
|
|
824
|
+
return null;
|
|
825
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR");
|
|
826
|
+
const root = await this.findRoot(word);
|
|
827
|
+
if (!root) {
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
word,
|
|
832
|
+
root,
|
|
833
|
+
isInflected: root !== clean,
|
|
834
|
+
candidates: getStemCandidates(word)
|
|
835
|
+
};
|
|
836
|
+
}
|
|
256
837
|
/**
|
|
257
838
|
* Returns a list of proverbs and idioms containing the word.
|
|
258
839
|
*/
|
|
@@ -469,6 +1050,17 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
469
1050
|
return { isCorrect: false, word, suggestion: mixMatch.dogru };
|
|
470
1051
|
}
|
|
471
1052
|
}
|
|
1053
|
+
const root = await this.findRoot(word);
|
|
1054
|
+
if (root) {
|
|
1055
|
+
const cleanWord2 = word.trim().toLocaleLowerCase("tr-TR");
|
|
1056
|
+
const isInflected = root !== cleanWord2;
|
|
1057
|
+
return {
|
|
1058
|
+
isCorrect: true,
|
|
1059
|
+
word,
|
|
1060
|
+
isInflected,
|
|
1061
|
+
root
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
472
1064
|
if (this.autocompleteCache.length === 0) {
|
|
473
1065
|
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
474
1066
|
}
|
|
@@ -965,13 +1557,28 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
965
1557
|
const unique = [...new Set(words)];
|
|
966
1558
|
const analyses = [];
|
|
967
1559
|
for (const word of unique) {
|
|
968
|
-
|
|
969
|
-
|
|
1560
|
+
let results = await this.getWord(word);
|
|
1561
|
+
let found = results.length > 0;
|
|
1562
|
+
let root;
|
|
1563
|
+
let isInflected;
|
|
1564
|
+
if (!found) {
|
|
1565
|
+
const resolvedRoot = await this.findRoot(word);
|
|
1566
|
+
if (resolvedRoot) {
|
|
1567
|
+
results = await this.getWord(resolvedRoot);
|
|
1568
|
+
if (results.length > 0) {
|
|
1569
|
+
found = true;
|
|
1570
|
+
root = resolvedRoot;
|
|
1571
|
+
isInflected = true;
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
970
1575
|
analyses.push({
|
|
971
1576
|
word,
|
|
972
1577
|
found,
|
|
973
1578
|
meaning: found ? this.firstMeaning(results) : null,
|
|
974
|
-
origin: found ? results[0].lisan || "T\xFCrk\xE7e" : null
|
|
1579
|
+
origin: found ? results[0].lisan || "T\xFCrk\xE7e" : null,
|
|
1580
|
+
root,
|
|
1581
|
+
isInflected
|
|
975
1582
|
});
|
|
976
1583
|
await this.delay(200);
|
|
977
1584
|
}
|
|
@@ -1069,5 +1676,12 @@ export {
|
|
|
1069
1676
|
TDKError,
|
|
1070
1677
|
TDKValidationError,
|
|
1071
1678
|
TDKNetworkError,
|
|
1679
|
+
TURKISH_VOWELS,
|
|
1680
|
+
isVowel,
|
|
1681
|
+
TURKISH_SUFFIXES,
|
|
1682
|
+
restoreConsonantSoftening,
|
|
1683
|
+
restoreVowelDrop,
|
|
1684
|
+
restoreInfinitive,
|
|
1685
|
+
getStemCandidates,
|
|
1072
1686
|
TDK
|
|
1073
1687
|
};
|