tdk-api-wrapper 1.3.1 → 1.5.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 +52 -15
- package/dist/{chunk-ACMGCL7T.mjs → chunk-7KJHYRJZ.mjs} +1041 -16
- package/dist/cli.js +1205 -21
- package/dist/cli.mjs +219 -7
- package/dist/index.d.mts +206 -1
- package/dist/index.d.ts +206 -1
- package/dist/index.js +1052 -17
- package/dist/index.mjs +23 -3
- package/package.json +3 -2
- package/src/cli.ts +224 -6
- package/src/index.ts +2 -1
- package/src/morphology.ts +324 -0
- package/src/tdk.ts +560 -15
- package/src/types.ts +49 -0
- package/test/grammar.test.js +52 -0
- package/test/morphology.test.js +129 -0
- package/test/proofread.test.js +60 -0
- package/test/tools.test.js +51 -0
package/dist/index.js
CHANGED
|
@@ -31,9 +31,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
TDK: () => TDK,
|
|
34
|
+
TDKClient: () => TDKClient,
|
|
34
35
|
TDKError: () => TDKError,
|
|
35
36
|
TDKNetworkError: () => TDKNetworkError,
|
|
36
|
-
TDKValidationError: () => TDKValidationError
|
|
37
|
+
TDKValidationError: () => TDKValidationError,
|
|
38
|
+
TURKISH_SUFFIXES: () => TURKISH_SUFFIXES,
|
|
39
|
+
TURKISH_VOWELS: () => TURKISH_VOWELS,
|
|
40
|
+
getStemCandidates: () => getStemCandidates,
|
|
41
|
+
isVowel: () => isVowel,
|
|
42
|
+
restoreConsonantSoftening: () => restoreConsonantSoftening,
|
|
43
|
+
restoreGemination: () => restoreGemination,
|
|
44
|
+
restoreInfinitive: () => restoreInfinitive,
|
|
45
|
+
restoreVowelDrop: () => restoreVowelDrop,
|
|
46
|
+
restoreVowelNarrowing: () => restoreVowelNarrowing
|
|
37
47
|
});
|
|
38
48
|
module.exports = __toCommonJS(src_exports);
|
|
39
49
|
|
|
@@ -64,6 +74,559 @@ var TDKNetworkError = class extends TDKError {
|
|
|
64
74
|
}
|
|
65
75
|
};
|
|
66
76
|
|
|
77
|
+
// src/morphology.ts
|
|
78
|
+
var TURKISH_VOWELS = "ae\u0131io\xF6u\xFC";
|
|
79
|
+
function isVowel(ch) {
|
|
80
|
+
return TURKISH_VOWELS.includes(ch);
|
|
81
|
+
}
|
|
82
|
+
var TURKISH_SUFFIXES = [
|
|
83
|
+
// 9-letter composite suffixes
|
|
84
|
+
"lerimizden",
|
|
85
|
+
"lar\u0131m\u0131zdan",
|
|
86
|
+
"lerinizden",
|
|
87
|
+
"lar\u0131n\u0131zdan",
|
|
88
|
+
// 8-letter composite suffixes
|
|
89
|
+
"lerinin",
|
|
90
|
+
"lar\u0131n\u0131n",
|
|
91
|
+
"lerinde",
|
|
92
|
+
"lar\u0131nda",
|
|
93
|
+
"lerinden",
|
|
94
|
+
"lar\u0131ndan",
|
|
95
|
+
"leriyle",
|
|
96
|
+
"lar\u0131yla",
|
|
97
|
+
"lerini",
|
|
98
|
+
"lar\u0131n\u0131",
|
|
99
|
+
"lerimize",
|
|
100
|
+
"lar\u0131m\u0131za",
|
|
101
|
+
"lerimizle",
|
|
102
|
+
"lar\u0131m\u0131zla",
|
|
103
|
+
"lerinizin",
|
|
104
|
+
"lar\u0131n\u0131z\u0131n",
|
|
105
|
+
"lerinizde",
|
|
106
|
+
"lar\u0131n\u0131zda",
|
|
107
|
+
"d\u0131\u011F\u0131ndan",
|
|
108
|
+
"di\u011Finden",
|
|
109
|
+
"du\u011Fundan",
|
|
110
|
+
"d\xFC\u011F\xFCnden",
|
|
111
|
+
"t\u0131\u011F\u0131ndan",
|
|
112
|
+
"ti\u011Finden",
|
|
113
|
+
"tu\u011Fundan",
|
|
114
|
+
"t\xFC\u011F\xFCnden",
|
|
115
|
+
// 7-letter composite suffixes
|
|
116
|
+
"ecektir",
|
|
117
|
+
"acakt\u0131r",
|
|
118
|
+
"ece\u011Fim",
|
|
119
|
+
"aca\u011F\u0131m",
|
|
120
|
+
"eceksin",
|
|
121
|
+
"acaks\u0131n",
|
|
122
|
+
"ece\u011Fiz",
|
|
123
|
+
"aca\u011F\u0131z",
|
|
124
|
+
"lerimiz",
|
|
125
|
+
"lar\u0131m\u0131z",
|
|
126
|
+
"leriniz",
|
|
127
|
+
"lar\u0131n\u0131z",
|
|
128
|
+
"umuzdan",
|
|
129
|
+
"\xFCm\xFCzden",
|
|
130
|
+
"inizden",
|
|
131
|
+
"\u0131n\u0131zdan",
|
|
132
|
+
"\xFCn\xFCzden",
|
|
133
|
+
"d\u0131\u011F\u0131nda",
|
|
134
|
+
"di\u011Finde",
|
|
135
|
+
"du\u011Funda",
|
|
136
|
+
"d\xFC\u011F\xFCnde",
|
|
137
|
+
"t\u0131\u011F\u0131nda",
|
|
138
|
+
"ti\u011Finde",
|
|
139
|
+
"tu\u011Funda",
|
|
140
|
+
"t\xFC\u011F\xFCnde",
|
|
141
|
+
"mas\u0131na",
|
|
142
|
+
"mesine",
|
|
143
|
+
"\u0131yorsunuz",
|
|
144
|
+
"iyorsunuz",
|
|
145
|
+
"uyorsunuz",
|
|
146
|
+
"\xFCyorsunuz",
|
|
147
|
+
"yorsunuz",
|
|
148
|
+
// 6-letter composite suffixes
|
|
149
|
+
"iyorsa",
|
|
150
|
+
"iyorduk",
|
|
151
|
+
"iyordu",
|
|
152
|
+
"iyormu\u015F",
|
|
153
|
+
"\u0131yorsa",
|
|
154
|
+
"\u0131yorduk",
|
|
155
|
+
"\u0131yordu",
|
|
156
|
+
"\u0131yormu\u015F",
|
|
157
|
+
"uyorsa",
|
|
158
|
+
"uyorduk",
|
|
159
|
+
"uyordu",
|
|
160
|
+
"uyormu\u015F",
|
|
161
|
+
"\xFCyorsa",
|
|
162
|
+
"\xFCyorduk",
|
|
163
|
+
"\xFCyordu",
|
|
164
|
+
"\xFCyormu\u015F",
|
|
165
|
+
"\u0131yorsun",
|
|
166
|
+
"iyorsun",
|
|
167
|
+
"uyorsun",
|
|
168
|
+
"\xFCyorsun",
|
|
169
|
+
"\u0131yorlar",
|
|
170
|
+
"iyorlar",
|
|
171
|
+
"uyorlar",
|
|
172
|
+
"\xFCyorlar",
|
|
173
|
+
"iyoruz",
|
|
174
|
+
"\u0131yoruz",
|
|
175
|
+
"uyoruz",
|
|
176
|
+
"\xFCyoruz",
|
|
177
|
+
"imizin",
|
|
178
|
+
"\u0131m\u0131z\u0131n",
|
|
179
|
+
"umuzun",
|
|
180
|
+
"\xFCm\xFCz\xFCn",
|
|
181
|
+
"imizde",
|
|
182
|
+
"\u0131m\u0131zda",
|
|
183
|
+
"umuzda",
|
|
184
|
+
"\xFCm\xFCzde",
|
|
185
|
+
"imizden",
|
|
186
|
+
"\u0131m\u0131zdan",
|
|
187
|
+
"imizle",
|
|
188
|
+
"\u0131m\u0131zla",
|
|
189
|
+
"umuzla",
|
|
190
|
+
"\xFCm\xFCzle",
|
|
191
|
+
"lerdir",
|
|
192
|
+
"lard\u0131r",
|
|
193
|
+
"mu\u015Ftur",
|
|
194
|
+
"mi\u015Ftir",
|
|
195
|
+
"mu\u015Ftur",
|
|
196
|
+
"m\xFC\u015Ft\xFCr",
|
|
197
|
+
"lerden",
|
|
198
|
+
"lardan",
|
|
199
|
+
"lerine",
|
|
200
|
+
"lar\u0131na",
|
|
201
|
+
"leriyle",
|
|
202
|
+
"lar\u0131yla",
|
|
203
|
+
"seniz",
|
|
204
|
+
"san\u0131z",
|
|
205
|
+
"diniz",
|
|
206
|
+
"d\u0131n\u0131z",
|
|
207
|
+
"dunuz",
|
|
208
|
+
"d\xFCn\xFCz",
|
|
209
|
+
"tiniz",
|
|
210
|
+
"t\u0131n\u0131z",
|
|
211
|
+
"tunuz",
|
|
212
|
+
"t\xFCn\xFCz",
|
|
213
|
+
"siniz",
|
|
214
|
+
"s\u0131n\u0131z",
|
|
215
|
+
"sunuz",
|
|
216
|
+
"s\xFCn\xFCz",
|
|
217
|
+
"d\u0131k\xE7a",
|
|
218
|
+
"dik\xE7e",
|
|
219
|
+
"duk\xE7a",
|
|
220
|
+
"d\xFCk\xE7e",
|
|
221
|
+
"t\u0131k\xE7a",
|
|
222
|
+
"tik\xE7e",
|
|
223
|
+
"tuk\xE7a",
|
|
224
|
+
"t\xFCk\xE7e",
|
|
225
|
+
"\u0131rken",
|
|
226
|
+
"irken",
|
|
227
|
+
"urken",
|
|
228
|
+
"\xFCrken",
|
|
229
|
+
"arken",
|
|
230
|
+
"erken",
|
|
231
|
+
// 5-letter suffixes
|
|
232
|
+
"lerde",
|
|
233
|
+
"larda",
|
|
234
|
+
"lerle",
|
|
235
|
+
"larla",
|
|
236
|
+
"lerin",
|
|
237
|
+
"lar\u0131n",
|
|
238
|
+
"lerim",
|
|
239
|
+
"lar\u0131m",
|
|
240
|
+
"dirler",
|
|
241
|
+
"d\u0131rlar",
|
|
242
|
+
"d\xFCrler",
|
|
243
|
+
"durlar",
|
|
244
|
+
"tirler",
|
|
245
|
+
"t\u0131rlar",
|
|
246
|
+
"t\xFCrler",
|
|
247
|
+
"turlar",
|
|
248
|
+
"siniz",
|
|
249
|
+
"s\u0131n\u0131z",
|
|
250
|
+
"sunuz",
|
|
251
|
+
"s\xFCn\xFCz",
|
|
252
|
+
"yorum",
|
|
253
|
+
"yorsun",
|
|
254
|
+
"uyoruz",
|
|
255
|
+
"yorsunuz",
|
|
256
|
+
"yorlar",
|
|
257
|
+
"eceks",
|
|
258
|
+
"acaks",
|
|
259
|
+
"eyim",
|
|
260
|
+
"ay\u0131m",
|
|
261
|
+
"indik",
|
|
262
|
+
"\u0131nd\u0131k",
|
|
263
|
+
"unduk",
|
|
264
|
+
"\xFCnd\xFCk",
|
|
265
|
+
"ildik",
|
|
266
|
+
"\u0131ld\u0131k",
|
|
267
|
+
"ulduk",
|
|
268
|
+
"\xFCld\xFCk",
|
|
269
|
+
"meden",
|
|
270
|
+
"madan",
|
|
271
|
+
"y\u0131n\u0131z",
|
|
272
|
+
"yiniz",
|
|
273
|
+
"yunuz",
|
|
274
|
+
"y\xFCn\xFCz",
|
|
275
|
+
// 4-letter suffixes
|
|
276
|
+
"imiz",
|
|
277
|
+
"\u0131m\u0131z",
|
|
278
|
+
"umuz",
|
|
279
|
+
"\xFCm\xFCz",
|
|
280
|
+
"iniz",
|
|
281
|
+
"\u0131n\u0131z",
|
|
282
|
+
"unuz",
|
|
283
|
+
"\xFCn\xFCz",
|
|
284
|
+
"leri",
|
|
285
|
+
"lar\u0131",
|
|
286
|
+
"idir",
|
|
287
|
+
"\u0131d\u0131r",
|
|
288
|
+
"udur",
|
|
289
|
+
"\xFCd\xFCr",
|
|
290
|
+
"ecek",
|
|
291
|
+
"acak",
|
|
292
|
+
"erek",
|
|
293
|
+
"arak",
|
|
294
|
+
"ince",
|
|
295
|
+
"\u0131nca",
|
|
296
|
+
"unca",
|
|
297
|
+
"\xFCnce",
|
|
298
|
+
"ken",
|
|
299
|
+
"meli",
|
|
300
|
+
"mal\u0131",
|
|
301
|
+
"iyor",
|
|
302
|
+
"\u0131yor",
|
|
303
|
+
"uyor",
|
|
304
|
+
"\xFCyor",
|
|
305
|
+
"mi\u015Fti",
|
|
306
|
+
"m\u0131\u015Ft\u0131",
|
|
307
|
+
"mu\u015Ftu",
|
|
308
|
+
"m\xFC\u015Ft\xFC",
|
|
309
|
+
"seydi",
|
|
310
|
+
"sayd\u0131",
|
|
311
|
+
"ydim",
|
|
312
|
+
"yd\u0131m",
|
|
313
|
+
"ydum",
|
|
314
|
+
"yd\xFCm",
|
|
315
|
+
"tiler",
|
|
316
|
+
"t\u0131lar",
|
|
317
|
+
"diler",
|
|
318
|
+
"d\u0131lar",
|
|
319
|
+
"ikten",
|
|
320
|
+
"\u0131ktan",
|
|
321
|
+
"uktan",
|
|
322
|
+
"\xFCkten",
|
|
323
|
+
// 3-letter suffixes
|
|
324
|
+
"ler",
|
|
325
|
+
"lar",
|
|
326
|
+
"den",
|
|
327
|
+
"dan",
|
|
328
|
+
"ten",
|
|
329
|
+
"tan",
|
|
330
|
+
"dir",
|
|
331
|
+
"d\u0131r",
|
|
332
|
+
"dur",
|
|
333
|
+
"d\xFCr",
|
|
334
|
+
"tir",
|
|
335
|
+
"t\u0131r",
|
|
336
|
+
"tur",
|
|
337
|
+
"t\xFCr",
|
|
338
|
+
"nin",
|
|
339
|
+
"n\u0131n",
|
|
340
|
+
"nun",
|
|
341
|
+
"n\xFCn",
|
|
342
|
+
"yle",
|
|
343
|
+
"yla",
|
|
344
|
+
"mi\u015F",
|
|
345
|
+
"m\u0131\u015F",
|
|
346
|
+
"mu\u015F",
|
|
347
|
+
"m\xFC\u015F",
|
|
348
|
+
"dim",
|
|
349
|
+
"d\u0131m",
|
|
350
|
+
"dum",
|
|
351
|
+
"d\xFCm",
|
|
352
|
+
"tim",
|
|
353
|
+
"t\u0131m",
|
|
354
|
+
"tum",
|
|
355
|
+
"t\xFCm",
|
|
356
|
+
"din",
|
|
357
|
+
"d\u0131n",
|
|
358
|
+
"dun",
|
|
359
|
+
"d\xFCn",
|
|
360
|
+
"tin",
|
|
361
|
+
"t\u0131n",
|
|
362
|
+
"tun",
|
|
363
|
+
"t\xFCn",
|
|
364
|
+
"dik",
|
|
365
|
+
"d\u0131k",
|
|
366
|
+
"duk",
|
|
367
|
+
"d\xFCk",
|
|
368
|
+
"tik",
|
|
369
|
+
"t\u0131k",
|
|
370
|
+
"tuk",
|
|
371
|
+
"t\xFCk",
|
|
372
|
+
"ydi",
|
|
373
|
+
"yd\u0131",
|
|
374
|
+
"ydu",
|
|
375
|
+
"yd\xFC",
|
|
376
|
+
"yim",
|
|
377
|
+
"y\u0131m",
|
|
378
|
+
"yum",
|
|
379
|
+
"y\xFCm",
|
|
380
|
+
"sin",
|
|
381
|
+
"s\u0131n",
|
|
382
|
+
"sun",
|
|
383
|
+
"s\xFCn",
|
|
384
|
+
"sen",
|
|
385
|
+
"san",
|
|
386
|
+
"sem",
|
|
387
|
+
"sam",
|
|
388
|
+
"sek",
|
|
389
|
+
"sak",
|
|
390
|
+
"siz",
|
|
391
|
+
"s\u0131z",
|
|
392
|
+
"suz",
|
|
393
|
+
"s\xFCz",
|
|
394
|
+
"lik",
|
|
395
|
+
"l\u0131k",
|
|
396
|
+
"luk",
|
|
397
|
+
"l\xFCk",
|
|
398
|
+
"ici",
|
|
399
|
+
"\u0131c\u0131",
|
|
400
|
+
"ucu",
|
|
401
|
+
"\xFCc\xFC",
|
|
402
|
+
"gen",
|
|
403
|
+
"gan",
|
|
404
|
+
"ken",
|
|
405
|
+
"kan",
|
|
406
|
+
"len",
|
|
407
|
+
"lan",
|
|
408
|
+
"le\u015F",
|
|
409
|
+
"la\u015F",
|
|
410
|
+
"mek",
|
|
411
|
+
"mak",
|
|
412
|
+
"yor",
|
|
413
|
+
// 2-letter suffixes
|
|
414
|
+
"de",
|
|
415
|
+
"da",
|
|
416
|
+
"te",
|
|
417
|
+
"ta",
|
|
418
|
+
"im",
|
|
419
|
+
"\u0131m",
|
|
420
|
+
"um",
|
|
421
|
+
"\xFCm",
|
|
422
|
+
"in",
|
|
423
|
+
"\u0131n",
|
|
424
|
+
"un",
|
|
425
|
+
"\xFCn",
|
|
426
|
+
"iz",
|
|
427
|
+
"\u0131z",
|
|
428
|
+
"uz",
|
|
429
|
+
"\xFCz",
|
|
430
|
+
"si",
|
|
431
|
+
"s\u0131",
|
|
432
|
+
"su",
|
|
433
|
+
"s\xFC",
|
|
434
|
+
"ye",
|
|
435
|
+
"ya",
|
|
436
|
+
"le",
|
|
437
|
+
"la",
|
|
438
|
+
"di",
|
|
439
|
+
"d\u0131",
|
|
440
|
+
"du",
|
|
441
|
+
"d\xFC",
|
|
442
|
+
"ti",
|
|
443
|
+
"t\u0131",
|
|
444
|
+
"tu",
|
|
445
|
+
"t\xFC",
|
|
446
|
+
"se",
|
|
447
|
+
"sa",
|
|
448
|
+
"ce",
|
|
449
|
+
"ca",
|
|
450
|
+
"\xE7e",
|
|
451
|
+
"\xE7a",
|
|
452
|
+
"me",
|
|
453
|
+
"ma",
|
|
454
|
+
"ip",
|
|
455
|
+
"\u0131p",
|
|
456
|
+
"up",
|
|
457
|
+
"\xFCp",
|
|
458
|
+
"en",
|
|
459
|
+
"an",
|
|
460
|
+
"i\u015F",
|
|
461
|
+
"\u0131\u015F",
|
|
462
|
+
"u\u015F",
|
|
463
|
+
"\xFC\u015F",
|
|
464
|
+
"li",
|
|
465
|
+
"l\u0131",
|
|
466
|
+
"lu",
|
|
467
|
+
"l\xFC",
|
|
468
|
+
"ci",
|
|
469
|
+
"c\u0131",
|
|
470
|
+
"cu",
|
|
471
|
+
"c\xFC",
|
|
472
|
+
"\xE7i",
|
|
473
|
+
"\xE7\u0131",
|
|
474
|
+
"\xE7u",
|
|
475
|
+
"\xE7\xFC",
|
|
476
|
+
// 1-letter suffixes (vowels / basic case endings)
|
|
477
|
+
"e",
|
|
478
|
+
"a",
|
|
479
|
+
"i",
|
|
480
|
+
"\u0131",
|
|
481
|
+
"u",
|
|
482
|
+
"\xFC"
|
|
483
|
+
];
|
|
484
|
+
function restoreConsonantSoftening(stem) {
|
|
485
|
+
if (stem.length < 2)
|
|
486
|
+
return [];
|
|
487
|
+
const last = stem.slice(-1);
|
|
488
|
+
const base = stem.slice(0, -1);
|
|
489
|
+
switch (last) {
|
|
490
|
+
case "b":
|
|
491
|
+
return [base + "p"];
|
|
492
|
+
case "c":
|
|
493
|
+
return [base + "\xE7"];
|
|
494
|
+
case "d":
|
|
495
|
+
return [base + "t"];
|
|
496
|
+
case "\u011F":
|
|
497
|
+
return [base + "k"];
|
|
498
|
+
case "g":
|
|
499
|
+
return [base + "k"];
|
|
500
|
+
default:
|
|
501
|
+
return [];
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function restoreVowelDrop(stem) {
|
|
505
|
+
if (stem.length < 3)
|
|
506
|
+
return [];
|
|
507
|
+
const c1 = stem[stem.length - 2];
|
|
508
|
+
const c2 = stem[stem.length - 1];
|
|
509
|
+
if (!isVowel(c1) && !isVowel(c2)) {
|
|
510
|
+
const vowelsInBase = stem.slice(0, -2).split("").filter(isVowel);
|
|
511
|
+
if (vowelsInBase.length > 0) {
|
|
512
|
+
const lastVowel = vowelsInBase[vowelsInBase.length - 1];
|
|
513
|
+
let inserted = "i";
|
|
514
|
+
if ("a\u0131".includes(lastVowel))
|
|
515
|
+
inserted = "\u0131";
|
|
516
|
+
else if ("ei".includes(lastVowel))
|
|
517
|
+
inserted = "i";
|
|
518
|
+
else if ("ou".includes(lastVowel))
|
|
519
|
+
inserted = "u";
|
|
520
|
+
else if ("\xF6\xFC".includes(lastVowel))
|
|
521
|
+
inserted = "\xFC";
|
|
522
|
+
return [stem.slice(0, -1) + inserted + c2];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
function restoreGemination(stem) {
|
|
528
|
+
if (stem.length < 3)
|
|
529
|
+
return [];
|
|
530
|
+
const c1 = stem[stem.length - 2];
|
|
531
|
+
const c2 = stem[stem.length - 1];
|
|
532
|
+
if (c1 === c2 && !isVowel(c1)) {
|
|
533
|
+
const single = stem.slice(0, -1);
|
|
534
|
+
const hardened = restoreConsonantSoftening(single);
|
|
535
|
+
return [single, ...hardened];
|
|
536
|
+
}
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
function restoreVowelNarrowing(stem) {
|
|
540
|
+
if (stem.length < 2)
|
|
541
|
+
return [];
|
|
542
|
+
if (stem === "di")
|
|
543
|
+
return ["de"];
|
|
544
|
+
if (stem === "yi")
|
|
545
|
+
return ["ye"];
|
|
546
|
+
const lastChar = stem[stem.length - 1];
|
|
547
|
+
const isLastNarrow = "\u0131iu\xFC".includes(lastChar);
|
|
548
|
+
if (isLastNarrow) {
|
|
549
|
+
const vowelsInBase = stem.slice(0, -1).split("").filter(isVowel);
|
|
550
|
+
const lastVowel = vowelsInBase.length > 0 ? vowelsInBase[vowelsInBase.length - 1] : lastChar;
|
|
551
|
+
const widened = "a\u0131ou".includes(lastVowel) ? "a" : "e";
|
|
552
|
+
return [stem.slice(0, -1) + widened];
|
|
553
|
+
}
|
|
554
|
+
if (!isVowel(lastChar)) {
|
|
555
|
+
const vowelsInBase = stem.split("").filter(isVowel);
|
|
556
|
+
if (vowelsInBase.length > 0) {
|
|
557
|
+
const lastVowel = vowelsInBase[vowelsInBase.length - 1];
|
|
558
|
+
const widened = "a\u0131ou".includes(lastVowel) ? "a" : "e";
|
|
559
|
+
return [stem + widened];
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
function restoreInfinitive(stem) {
|
|
565
|
+
if (stem.length < 2)
|
|
566
|
+
return [];
|
|
567
|
+
const vowelsInBase = stem.split("").filter(isVowel);
|
|
568
|
+
if (vowelsInBase.length === 0)
|
|
569
|
+
return [];
|
|
570
|
+
const lastVowel = vowelsInBase[vowelsInBase.length - 1];
|
|
571
|
+
return "a\u0131ou".includes(lastVowel) ? [stem + "mak"] : [stem + "mek"];
|
|
572
|
+
}
|
|
573
|
+
function getStemCandidates(word, minStemLength = 2, maxDepth = 4) {
|
|
574
|
+
if (!word || word.trim().length === 0)
|
|
575
|
+
return [];
|
|
576
|
+
const raw = word.trim();
|
|
577
|
+
const normalized = raw.toLocaleLowerCase("tr-TR");
|
|
578
|
+
const candidatesWithWeight = [];
|
|
579
|
+
const seen = /* @__PURE__ */ new Set();
|
|
580
|
+
if (raw.includes("'") || raw.includes("\u2019")) {
|
|
581
|
+
const apostropheStem = normalized.split(/['’]/)[0];
|
|
582
|
+
if (apostropheStem.length >= minStemLength) {
|
|
583
|
+
candidatesWithWeight.push({ candidate: apostropheStem, baseLength: apostropheStem.length + 10 });
|
|
584
|
+
seen.add(apostropheStem);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
let frontier = [normalized];
|
|
588
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
589
|
+
const nextFrontier = [];
|
|
590
|
+
for (const current of frontier) {
|
|
591
|
+
for (const suffix of TURKISH_SUFFIXES) {
|
|
592
|
+
if (current.length - suffix.length >= minStemLength && current.endsWith(suffix)) {
|
|
593
|
+
const stem = current.slice(0, -suffix.length);
|
|
594
|
+
const hardened = restoreConsonantSoftening(stem);
|
|
595
|
+
const vowelDropped = restoreVowelDrop(stem);
|
|
596
|
+
const geminated = restoreGemination(stem);
|
|
597
|
+
const isNarrowingSuffix = suffix.startsWith("yor") || suffix.includes("iyor") || suffix.includes("\u0131yor") || suffix.includes("uyor") || suffix.includes("\xFCyor");
|
|
598
|
+
const isDeYeBuffer = (stem === "di" || stem === "yi") && suffix.startsWith("y");
|
|
599
|
+
const narrowed = isNarrowingSuffix || isDeYeBuffer ? restoreVowelNarrowing(stem) : [];
|
|
600
|
+
const isVerbSuffix = isNarrowingSuffix || suffix.includes("ecek") || suffix.includes("acak") || suffix.includes("mi\u015F") || suffix.includes("m\u0131\u015F") || suffix.includes("m\xFC\u015F") || suffix.includes("mu\u015F") || suffix.includes("mek") || suffix.includes("mak") || suffix.includes("erek") || suffix.includes("arak") || suffix.includes("dik") || suffix.includes("d\u0131k") || suffix.includes("duk") || suffix.includes("d\xFCk") || suffix.includes("tik") || suffix.includes("t\u0131k") || suffix.includes("tuk") || suffix.includes("t\xFCk") || suffix.includes("sen") || suffix.includes("san") || suffix.includes("sem") || suffix.includes("sam") || suffix.includes("sek") || suffix.includes("sak");
|
|
601
|
+
const verbalBases = [stem, ...hardened, ...narrowed];
|
|
602
|
+
const infinitives = verbalBases.flatMap((v) => restoreInfinitive(v));
|
|
603
|
+
const variants = [stem, ...hardened, ...vowelDropped, ...geminated, ...narrowed];
|
|
604
|
+
for (const variant of variants) {
|
|
605
|
+
if (!seen.has(variant) && variant !== normalized) {
|
|
606
|
+
seen.add(variant);
|
|
607
|
+
nextFrontier.push(variant);
|
|
608
|
+
candidatesWithWeight.push({ candidate: variant, baseLength: stem.length });
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
for (const inf of infinitives) {
|
|
612
|
+
if (!seen.has(inf) && inf !== normalized) {
|
|
613
|
+
seen.add(inf);
|
|
614
|
+
nextFrontier.push(inf);
|
|
615
|
+
const weight = isVerbSuffix ? stem.length + 5 : stem.length;
|
|
616
|
+
candidatesWithWeight.push({ candidate: inf, baseLength: weight });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (nextFrontier.length === 0)
|
|
623
|
+
break;
|
|
624
|
+
frontier = nextFrontier;
|
|
625
|
+
}
|
|
626
|
+
candidatesWithWeight.sort((a, b) => b.baseLength - a.baseLength);
|
|
627
|
+
return [...new Set(candidatesWithWeight.map((c) => c.candidate))];
|
|
628
|
+
}
|
|
629
|
+
|
|
67
630
|
// src/tdk.ts
|
|
68
631
|
var fs = __toESM(require("fs"));
|
|
69
632
|
var path = __toESM(require("path"));
|
|
@@ -150,11 +713,30 @@ M71DMi+y1+TRSJVClEMwvA4yL++7q9XZx5r5wBRWB4kQTKH5qyoZnDw7iiuh1lID
|
|
|
150
713
|
yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
151
714
|
-----END CERTIFICATE-----`
|
|
152
715
|
];
|
|
716
|
+
// Configuration
|
|
717
|
+
static defaultTimeoutMs = 8e3;
|
|
718
|
+
static defaultRetries = 1;
|
|
719
|
+
static maxCacheSize = 1e3;
|
|
153
720
|
// Cache Mechanism
|
|
154
721
|
static isCacheEnabled = false;
|
|
155
722
|
static wordCache = /* @__PURE__ */ new Map();
|
|
156
723
|
static dailyContentCache = null;
|
|
157
724
|
static autocompleteCache = [];
|
|
725
|
+
static autocompleteSet = /* @__PURE__ */ new Set();
|
|
726
|
+
static stemCache = /* @__PURE__ */ new Map();
|
|
727
|
+
/**
|
|
728
|
+
* Configures global client options such as network timeout, retries, and cache size.
|
|
729
|
+
*/
|
|
730
|
+
static configure(config) {
|
|
731
|
+
if (config.timeoutMs !== void 0)
|
|
732
|
+
this.defaultTimeoutMs = Math.max(100, config.timeoutMs);
|
|
733
|
+
if (config.retries !== void 0)
|
|
734
|
+
this.defaultRetries = Math.max(0, config.retries);
|
|
735
|
+
if (config.cache !== void 0)
|
|
736
|
+
this.enableCache(config.cache);
|
|
737
|
+
if (config.maxCacheSize !== void 0)
|
|
738
|
+
this.maxCacheSize = Math.max(10, config.maxCacheSize);
|
|
739
|
+
}
|
|
158
740
|
/**
|
|
159
741
|
* Enables or disables in-memory caching for API requests.
|
|
160
742
|
*/
|
|
@@ -171,10 +753,53 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
171
753
|
this.wordCache.clear();
|
|
172
754
|
this.dailyContentCache = null;
|
|
173
755
|
this.autocompleteCache = [];
|
|
756
|
+
this.autocompleteSet.clear();
|
|
757
|
+
this.stemCache.clear();
|
|
758
|
+
}
|
|
759
|
+
static setBoundedCache(map, key, value) {
|
|
760
|
+
if (map.size >= this.maxCacheSize) {
|
|
761
|
+
const firstKey = map.keys().next().value;
|
|
762
|
+
if (firstKey !== void 0)
|
|
763
|
+
map.delete(firstKey);
|
|
764
|
+
}
|
|
765
|
+
map.set(key, value);
|
|
174
766
|
}
|
|
175
767
|
static delay(ms) {
|
|
176
768
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
177
769
|
}
|
|
770
|
+
/**
|
|
771
|
+
* Internal helper that performs HTTP fetch with timeout and automatic retry on network/5xx errors.
|
|
772
|
+
*/
|
|
773
|
+
static async fetchWithRetry(url, options = {}, retries = this.defaultRetries, timeoutMs = this.defaultTimeoutMs) {
|
|
774
|
+
let lastError;
|
|
775
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
776
|
+
try {
|
|
777
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
778
|
+
const headers = {
|
|
779
|
+
"User-Agent": "TDK-API-Nodejs-Wrapper/1.0",
|
|
780
|
+
...options.headers || {}
|
|
781
|
+
};
|
|
782
|
+
const res = await fetch(url, { ...options, headers, signal });
|
|
783
|
+
if (res.ok || res.status >= 400 && res.status < 500) {
|
|
784
|
+
return res;
|
|
785
|
+
}
|
|
786
|
+
if (attempt < retries) {
|
|
787
|
+
await this.delay(200 * (attempt + 1));
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
return res;
|
|
791
|
+
} catch (err) {
|
|
792
|
+
lastError = err;
|
|
793
|
+
if (attempt < retries) {
|
|
794
|
+
await this.delay(200 * (attempt + 1));
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
throw new TDKNetworkError(`Request to ${url} failed after ${retries + 1} attempts.`, {
|
|
800
|
+
cause: lastError
|
|
801
|
+
});
|
|
802
|
+
}
|
|
178
803
|
/**
|
|
179
804
|
* Fetches detailed information for a given word from the TDK Dictionary.
|
|
180
805
|
*/
|
|
@@ -189,9 +814,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
189
814
|
const url = `${this.BASE_URL}/gts?ara=${encodeURIComponent(cleanWord)}`;
|
|
190
815
|
let response;
|
|
191
816
|
try {
|
|
192
|
-
response = await
|
|
193
|
-
headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" }
|
|
194
|
-
});
|
|
817
|
+
response = await this.fetchWithRetry(url);
|
|
195
818
|
} catch (error) {
|
|
196
819
|
throw new TDKNetworkError("Failed to fetch word from TDK: request failed.", { cause: error });
|
|
197
820
|
}
|
|
@@ -208,12 +831,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
208
831
|
}
|
|
209
832
|
if (!Array.isArray(data) && data && "error" in data) {
|
|
210
833
|
if (this.isCacheEnabled)
|
|
211
|
-
this.wordCache
|
|
834
|
+
this.setBoundedCache(this.wordCache, cleanWord, []);
|
|
212
835
|
return [];
|
|
213
836
|
}
|
|
214
837
|
const results = data;
|
|
215
838
|
if (this.isCacheEnabled) {
|
|
216
|
-
this.wordCache
|
|
839
|
+
this.setBoundedCache(this.wordCache, cleanWord, results);
|
|
217
840
|
}
|
|
218
841
|
return results;
|
|
219
842
|
}
|
|
@@ -277,6 +900,17 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
277
900
|
return [];
|
|
278
901
|
}
|
|
279
902
|
}
|
|
903
|
+
/**
|
|
904
|
+
* Ensures TDK's ~81k headword list is loaded in memory for fast O(1) set operations.
|
|
905
|
+
*/
|
|
906
|
+
static async ensureAutocompleteLoaded() {
|
|
907
|
+
if (this.autocompleteCache.length === 0) {
|
|
908
|
+
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
909
|
+
this.autocompleteSet = new Set(
|
|
910
|
+
this.autocompleteCache.map((w) => w.toLocaleLowerCase("tr-TR"))
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
280
914
|
/**
|
|
281
915
|
* Returns autocomplete suggestions for a given prefix, searched over TDK's
|
|
282
916
|
* full headword list (see `fetchAutocompleteData`). The list is fetched
|
|
@@ -286,12 +920,81 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
286
920
|
static async getSuggestions(prefix) {
|
|
287
921
|
if (!prefix || prefix.trim() === "")
|
|
288
922
|
return [];
|
|
289
|
-
|
|
290
|
-
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
291
|
-
}
|
|
923
|
+
await this.ensureAutocompleteLoaded();
|
|
292
924
|
const cleanPrefix = prefix.trim().toLocaleLowerCase("tr-TR");
|
|
293
925
|
return this.autocompleteCache.filter((w) => w.toLocaleLowerCase("tr-TR").startsWith(cleanPrefix)).slice(0, 10);
|
|
294
926
|
}
|
|
927
|
+
/**
|
|
928
|
+
* Checks whether a word exists as a known headword in TDK dictionary.
|
|
929
|
+
* Checks in-memory autocompleteSet (81k headwords) if loaded, or queries TDK API.
|
|
930
|
+
*/
|
|
931
|
+
static async isHeadword(word) {
|
|
932
|
+
if (!word || word.trim() === "")
|
|
933
|
+
return false;
|
|
934
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR");
|
|
935
|
+
await this.ensureAutocompleteLoaded();
|
|
936
|
+
if (this.autocompleteSet.size > 0) {
|
|
937
|
+
return this.autocompleteSet.has(clean);
|
|
938
|
+
}
|
|
939
|
+
try {
|
|
940
|
+
const results = await this.getWord(clean);
|
|
941
|
+
return results.length > 0;
|
|
942
|
+
} catch {
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Generates candidate roots for a given Turkish word using progressive BFS suffix stripping,
|
|
948
|
+
* consonant mutation restoration, and vowel drop restoration.
|
|
949
|
+
*/
|
|
950
|
+
static getStemCandidates(word) {
|
|
951
|
+
return getStemCandidates(word);
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Finds the dictionary root (headword) of a word by checking direct existence
|
|
955
|
+
* and evaluating candidate stems generated by morphological analysis.
|
|
956
|
+
* Returns the root headword string if found, or null if no match in TDK.
|
|
957
|
+
*/
|
|
958
|
+
static async findRoot(word) {
|
|
959
|
+
if (!word || word.trim() === "")
|
|
960
|
+
return null;
|
|
961
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR");
|
|
962
|
+
if (this.stemCache.has(clean)) {
|
|
963
|
+
return this.stemCache.get(clean);
|
|
964
|
+
}
|
|
965
|
+
if (await this.isHeadword(clean)) {
|
|
966
|
+
this.setBoundedCache(this.stemCache, clean, clean);
|
|
967
|
+
return clean;
|
|
968
|
+
}
|
|
969
|
+
const candidates = getStemCandidates(clean);
|
|
970
|
+
for (const candidate of candidates) {
|
|
971
|
+
if (await this.isHeadword(candidate)) {
|
|
972
|
+
this.setBoundedCache(this.stemCache, clean, candidate);
|
|
973
|
+
return candidate;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
this.setBoundedCache(this.stemCache, clean, null);
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Performs morphological stemming on a Turkish word.
|
|
981
|
+
* Returns a StemResult containing the original word, resolved root, and whether it is inflected.
|
|
982
|
+
*/
|
|
983
|
+
static async stem(word) {
|
|
984
|
+
if (!word || word.trim() === "")
|
|
985
|
+
return null;
|
|
986
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR");
|
|
987
|
+
const root = await this.findRoot(word);
|
|
988
|
+
if (!root) {
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
return {
|
|
992
|
+
word,
|
|
993
|
+
root,
|
|
994
|
+
isInflected: root !== clean,
|
|
995
|
+
candidates: getStemCandidates(word)
|
|
996
|
+
};
|
|
997
|
+
}
|
|
295
998
|
/**
|
|
296
999
|
* Returns a list of proverbs and idioms containing the word.
|
|
297
1000
|
*/
|
|
@@ -508,6 +1211,17 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
508
1211
|
return { isCorrect: false, word, suggestion: mixMatch.dogru };
|
|
509
1212
|
}
|
|
510
1213
|
}
|
|
1214
|
+
const root = await this.findRoot(word);
|
|
1215
|
+
if (root) {
|
|
1216
|
+
const cleanWord2 = word.trim().toLocaleLowerCase("tr-TR");
|
|
1217
|
+
const isInflected = root !== cleanWord2;
|
|
1218
|
+
return {
|
|
1219
|
+
isCorrect: true,
|
|
1220
|
+
word,
|
|
1221
|
+
isInflected,
|
|
1222
|
+
root
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
511
1225
|
if (this.autocompleteCache.length === 0) {
|
|
512
1226
|
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
513
1227
|
}
|
|
@@ -935,14 +1649,16 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
935
1649
|
meaningCount: meaningsA.length,
|
|
936
1650
|
origin: originA,
|
|
937
1651
|
syllables: this.syllabicate(a),
|
|
938
|
-
harmony: this.checkVowelHarmony(a)
|
|
1652
|
+
harmony: this.checkVowelHarmony(a),
|
|
1653
|
+
labialHarmony: this.checkLabialHarmony(a)
|
|
939
1654
|
},
|
|
940
1655
|
b: {
|
|
941
1656
|
word: b,
|
|
942
1657
|
meaningCount: meaningsB.length,
|
|
943
1658
|
origin: originB,
|
|
944
1659
|
syllables: this.syllabicate(b),
|
|
945
|
-
harmony: this.checkVowelHarmony(b)
|
|
1660
|
+
harmony: this.checkVowelHarmony(b),
|
|
1661
|
+
labialHarmony: this.checkLabialHarmony(b)
|
|
946
1662
|
}
|
|
947
1663
|
};
|
|
948
1664
|
}
|
|
@@ -1004,13 +1720,28 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1004
1720
|
const unique = [...new Set(words)];
|
|
1005
1721
|
const analyses = [];
|
|
1006
1722
|
for (const word of unique) {
|
|
1007
|
-
|
|
1008
|
-
|
|
1723
|
+
let results = await this.getWord(word);
|
|
1724
|
+
let found = results.length > 0;
|
|
1725
|
+
let root;
|
|
1726
|
+
let isInflected;
|
|
1727
|
+
if (!found) {
|
|
1728
|
+
const resolvedRoot = await this.findRoot(word);
|
|
1729
|
+
if (resolvedRoot) {
|
|
1730
|
+
results = await this.getWord(resolvedRoot);
|
|
1731
|
+
if (results.length > 0) {
|
|
1732
|
+
found = true;
|
|
1733
|
+
root = resolvedRoot;
|
|
1734
|
+
isInflected = true;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1009
1738
|
analyses.push({
|
|
1010
1739
|
word,
|
|
1011
1740
|
found,
|
|
1012
1741
|
meaning: found ? this.firstMeaning(results) : null,
|
|
1013
|
-
origin: found ? results[0].lisan || "T\xFCrk\xE7e" : null
|
|
1742
|
+
origin: found ? results[0].lisan || "T\xFCrk\xE7e" : null,
|
|
1743
|
+
root,
|
|
1744
|
+
isInflected
|
|
1014
1745
|
});
|
|
1015
1746
|
await this.delay(200);
|
|
1016
1747
|
}
|
|
@@ -1058,9 +1789,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1058
1789
|
}
|
|
1059
1790
|
/**
|
|
1060
1791
|
* Syllabicates a Turkish word based on general grammar rules.
|
|
1792
|
+
* Handles syllable separation for vowels, single consonants, double consonants,
|
|
1793
|
+
* and western loanword three-consonant clusters (e.g. e-lek-trik, kon-trol, or-kes-tra).
|
|
1061
1794
|
*/
|
|
1062
1795
|
static syllabicate(word) {
|
|
1063
1796
|
const vowels = /[aeıioöuüAEIİOÖUÜ]/;
|
|
1797
|
+
const ONSET_CLUSTERS = /* @__PURE__ */ new Set(["tr", "pr", "kr", "gr", "br", "fr", "dr", "pl", "kl", "fl", "bl", "gl"]);
|
|
1064
1798
|
const result = [];
|
|
1065
1799
|
let currentSyllable = "";
|
|
1066
1800
|
for (let i = word.length - 1; i >= 0; i--) {
|
|
@@ -1071,8 +1805,13 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1071
1805
|
currentSyllable = word[i - 1] + currentSyllable;
|
|
1072
1806
|
i--;
|
|
1073
1807
|
} else if (i - 2 >= 0 && !vowels.test(word[i - 2])) {
|
|
1074
|
-
|
|
1075
|
-
|
|
1808
|
+
if (i - 3 >= 0 && !vowels.test(word[i - 3]) && ONSET_CLUSTERS.has((word[i - 2] + word[i - 1]).toLowerCase())) {
|
|
1809
|
+
currentSyllable = word[i - 2] + word[i - 1] + currentSyllable;
|
|
1810
|
+
i -= 2;
|
|
1811
|
+
} else {
|
|
1812
|
+
currentSyllable = word[i - 1] + currentSyllable;
|
|
1813
|
+
i--;
|
|
1814
|
+
}
|
|
1076
1815
|
}
|
|
1077
1816
|
}
|
|
1078
1817
|
result.unshift(currentSyllable);
|
|
@@ -1102,11 +1841,307 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1102
1841
|
const hasFront = frontVowels.test(lower);
|
|
1103
1842
|
return !(hasBack && hasFront);
|
|
1104
1843
|
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Checks if a word follows Turkish Minor Vowel Harmony (Küçük Ünlü Uyumu / Labial Harmony).
|
|
1846
|
+
* Rules:
|
|
1847
|
+
* 1. After an unrounded vowel (a, e, ı, i), only unrounded vowels (a, e, ı, i) can follow.
|
|
1848
|
+
* 2. After a rounded vowel (o, ö, u, ü), either an unrounded wide (a, e) or rounded narrow (u, ü) vowel can follow.
|
|
1849
|
+
* Single-syllable words and words with <=1 vowel are considered compliant by convention.
|
|
1850
|
+
*/
|
|
1851
|
+
static checkLabialHarmony(word) {
|
|
1852
|
+
const lower = word.toLocaleLowerCase("tr-TR");
|
|
1853
|
+
const vowels = lower.split("").filter((ch) => "ae\u0131io\xF6u\xFC".includes(ch));
|
|
1854
|
+
if (vowels.length <= 1)
|
|
1855
|
+
return true;
|
|
1856
|
+
for (let i = 0; i < vowels.length - 1; i++) {
|
|
1857
|
+
const v1 = vowels[i];
|
|
1858
|
+
const v2 = vowels[i + 1];
|
|
1859
|
+
if ("ae\u0131i".includes(v1)) {
|
|
1860
|
+
if (!"ae\u0131i".includes(v2))
|
|
1861
|
+
return false;
|
|
1862
|
+
} else if ("o\xF6u\xFC".includes(v1)) {
|
|
1863
|
+
if (!"aeu\xFC".includes(v2))
|
|
1864
|
+
return false;
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
return true;
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Searches TDK headwords using a wildcard / pattern string.
|
|
1871
|
+
* Wildcards:
|
|
1872
|
+
* '_' or '?' matches any single character
|
|
1873
|
+
* '*' matches zero or more characters
|
|
1874
|
+
* Example: "k_l_m" matches "kalem", "kelam", "kilim".
|
|
1875
|
+
* Runs in-memory against TDK's 81k headword list.
|
|
1876
|
+
*/
|
|
1877
|
+
static async patternSearch(pattern, options) {
|
|
1878
|
+
if (!pattern || pattern.trim() === "")
|
|
1879
|
+
return [];
|
|
1880
|
+
await this.ensureAutocompleteLoaded();
|
|
1881
|
+
const cleanPattern = pattern.trim().toLocaleLowerCase("tr-TR");
|
|
1882
|
+
const escaped = cleanPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/[_?]/g, "[\\p{L}]").replace(/\*/g, "[\\p{L}]*");
|
|
1883
|
+
const regex = new RegExp(`^${escaped}$`, "u");
|
|
1884
|
+
const max = options?.maxResults ?? 50;
|
|
1885
|
+
const matches = [];
|
|
1886
|
+
for (const headword of this.autocompleteCache) {
|
|
1887
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
1888
|
+
if (regex.test(lower)) {
|
|
1889
|
+
matches.push(headword);
|
|
1890
|
+
if (matches.length >= max)
|
|
1891
|
+
break;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return matches;
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1897
|
+
* Finds headwords in TDK that can be formed from the given letters (anagrams).
|
|
1898
|
+
* If exact-length anagrams exist, they are returned.
|
|
1899
|
+
* If none exist (or exactLength is false), valid sub-anagrams (words using a subset of the letters,
|
|
1900
|
+
* minimum 3 letters) are returned, sorted by length descending.
|
|
1901
|
+
*/
|
|
1902
|
+
static async findAnagrams(letters, options) {
|
|
1903
|
+
if (!letters || letters.trim() === "")
|
|
1904
|
+
return [];
|
|
1905
|
+
await this.ensureAutocompleteLoaded();
|
|
1906
|
+
const clean = letters.trim().toLocaleLowerCase("tr-TR").replace(/[^a-zçğıöşüâîû]/gi, "");
|
|
1907
|
+
if (clean.length === 0)
|
|
1908
|
+
return [];
|
|
1909
|
+
const forceExact = options?.exactLength === true;
|
|
1910
|
+
const max = options?.maxResults ?? 50;
|
|
1911
|
+
const getFrequency = (str) => {
|
|
1912
|
+
const freq = {};
|
|
1913
|
+
for (const ch of str) {
|
|
1914
|
+
freq[ch] = (freq[ch] || 0) + 1;
|
|
1915
|
+
}
|
|
1916
|
+
return freq;
|
|
1917
|
+
};
|
|
1918
|
+
const targetFreq = getFrequency(clean);
|
|
1919
|
+
const exactMatches = [];
|
|
1920
|
+
const subMatches = [];
|
|
1921
|
+
for (const headword of this.autocompleteCache) {
|
|
1922
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
1923
|
+
if (lower.includes(" ") || lower.includes("-"))
|
|
1924
|
+
continue;
|
|
1925
|
+
if (lower.length > clean.length || lower.length < 3)
|
|
1926
|
+
continue;
|
|
1927
|
+
const wordFreq = getFrequency(lower);
|
|
1928
|
+
let isValid = true;
|
|
1929
|
+
for (const [ch, count] of Object.entries(wordFreq)) {
|
|
1930
|
+
if (!targetFreq[ch] || targetFreq[ch] < count) {
|
|
1931
|
+
isValid = false;
|
|
1932
|
+
break;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
if (isValid && lower !== clean) {
|
|
1936
|
+
if (lower.length === clean.length) {
|
|
1937
|
+
exactMatches.push(headword);
|
|
1938
|
+
} else {
|
|
1939
|
+
subMatches.push(headword);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
if (exactMatches.length > 0 || forceExact) {
|
|
1944
|
+
return exactMatches.slice(0, max);
|
|
1945
|
+
}
|
|
1946
|
+
subMatches.sort((a, b) => b.length - a.length || a.localeCompare(b, "tr-TR"));
|
|
1947
|
+
return subMatches.slice(0, max);
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Finds words in TDK that rhyme with the given word (sharing the same ending suffix/letters).
|
|
1951
|
+
* @param word The target word
|
|
1952
|
+
* @param options.minLetters Minimum number of ending characters that must match (default: 3)
|
|
1953
|
+
* @param options.maxResults Maximum number of rhyme results to return (default: 50)
|
|
1954
|
+
*/
|
|
1955
|
+
static async findRhymes(word, options) {
|
|
1956
|
+
if (!word || word.trim() === "")
|
|
1957
|
+
return [];
|
|
1958
|
+
await this.ensureAutocompleteLoaded();
|
|
1959
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR");
|
|
1960
|
+
const minLetters = Math.min(options?.minLetters ?? 3, clean.length);
|
|
1961
|
+
const max = options?.maxResults ?? 50;
|
|
1962
|
+
const suffix = clean.slice(-minLetters);
|
|
1963
|
+
const results = [];
|
|
1964
|
+
for (const headword of this.autocompleteCache) {
|
|
1965
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
1966
|
+
if (lower !== clean && lower.endsWith(suffix) && !lower.includes(" ")) {
|
|
1967
|
+
results.push(headword);
|
|
1968
|
+
if (results.length >= max)
|
|
1969
|
+
break;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
return results;
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Performs comprehensive spelling, grammar, and syntax proofreading on a Turkish text.
|
|
1976
|
+
* Detects:
|
|
1977
|
+
* 1. Conjunction 'da/de' erroneously joined to verbs or words (e.g. "gitsende" -> "gitsen de")
|
|
1978
|
+
* 2. Conjunction 'ki' erroneously joined to verbs (e.g. "gördümki" -> "gördüm ki"), respecting SOMBAHÇEMİ exceptions
|
|
1979
|
+
* 3. Question particle 'mi/mı/mu/mü' erroneously joined to words (e.g. "geldimi" -> "geldi mi")
|
|
1980
|
+
* 4. Misspelled words with dictionary suggestions (via edit-distance & morphology)
|
|
1981
|
+
*/
|
|
1982
|
+
static async proofread(text) {
|
|
1983
|
+
if (!text || text.trim() === "") {
|
|
1984
|
+
return { text: text || "", issues: [], isCorrect: true };
|
|
1985
|
+
}
|
|
1986
|
+
await this.ensureAutocompleteLoaded();
|
|
1987
|
+
const issues = [];
|
|
1988
|
+
const SOMBAHCEMI = /* @__PURE__ */ new Set([
|
|
1989
|
+
"sanki",
|
|
1990
|
+
"oysaki",
|
|
1991
|
+
"mademki",
|
|
1992
|
+
"belki",
|
|
1993
|
+
"halbuki",
|
|
1994
|
+
"\xE7\xFCnk\xFC",
|
|
1995
|
+
"me\u011Ferki",
|
|
1996
|
+
"illaki"
|
|
1997
|
+
]);
|
|
1998
|
+
const tokenRegex = /[\p{L}0-9'’]+/gu;
|
|
1999
|
+
let match;
|
|
2000
|
+
while ((match = tokenRegex.exec(text)) !== null) {
|
|
2001
|
+
const rawWord = match[0];
|
|
2002
|
+
const startIndex = match.index;
|
|
2003
|
+
const endIndex = startIndex + rawWord.length;
|
|
2004
|
+
const lower = rawWord.toLocaleLowerCase("tr-TR");
|
|
2005
|
+
if (/^\d+$/.test(lower))
|
|
2006
|
+
continue;
|
|
2007
|
+
let flagged = false;
|
|
2008
|
+
const questionMatch = lower.match(/^(.+?)(m[ıiuü](?:sin|sın|sun|sün|siniz|sınız|sunuz|sünüz|yiz|yız|yuz|yüz|m|k)?)$/);
|
|
2009
|
+
if (questionMatch) {
|
|
2010
|
+
const base = questionMatch[1];
|
|
2011
|
+
const particle = questionMatch[2];
|
|
2012
|
+
if (base.length >= 2 && (await this.isHeadword(base) || await this.findRoot(base) !== null)) {
|
|
2013
|
+
if (!await this.isHeadword(lower)) {
|
|
2014
|
+
issues.push({
|
|
2015
|
+
type: "question_particle",
|
|
2016
|
+
word: rawWord,
|
|
2017
|
+
startIndex,
|
|
2018
|
+
endIndex,
|
|
2019
|
+
suggestion: `${base} ${particle}`,
|
|
2020
|
+
message: `'${particle}' soru eki kendinden \xF6nceki kelimeden ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
|
|
2021
|
+
});
|
|
2022
|
+
flagged = true;
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
const VERB_CONJUGATION_REGEX = /(?:d[ıiuü][kmmn]?|t[ıiuü][kmmn]?|d[ıiuü]n[ıiuü]z?|t[ıiuü]n[ıiuü]z?|m[ıiuü]ş(?:[szn][ıiuü]z?|lar)?|yor(?:um|sun|uz|lar)?|ecek(?:sin|iz|ler)?|acak(?:sın|ız|lar)?|s[ae][mnk]|s[ae]n[ıiz]?|meli|malı|me[mz]|ma[mz])$/i;
|
|
2027
|
+
if (!flagged && lower.endsWith("ki") && lower.length > 3) {
|
|
2028
|
+
const base = lower.slice(0, -2);
|
|
2029
|
+
if (!SOMBAHCEMI.has(lower)) {
|
|
2030
|
+
if (!await this.isHeadword(lower)) {
|
|
2031
|
+
const root = await this.findRoot(base);
|
|
2032
|
+
const isVerb = root && (root.endsWith("mek") || root.endsWith("mak")) || base === "demek" || base === "kald\u0131" || base === "yeter" || base === "bilmem" || VERB_CONJUGATION_REGEX.test(base);
|
|
2033
|
+
if (isVerb) {
|
|
2034
|
+
issues.push({
|
|
2035
|
+
type: "conjunction_ki",
|
|
2036
|
+
word: rawWord,
|
|
2037
|
+
startIndex,
|
|
2038
|
+
endIndex,
|
|
2039
|
+
suggestion: `${base} ki`,
|
|
2040
|
+
message: `'ki' ba\u011Flac\u0131 ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
|
|
2041
|
+
});
|
|
2042
|
+
flagged = true;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
if (!flagged && (lower.endsWith("de") || lower.endsWith("da") || lower.endsWith("te") || lower.endsWith("ta")) && lower.length > 3) {
|
|
2048
|
+
const base = lower.slice(0, -2);
|
|
2049
|
+
const ending = lower.slice(-2);
|
|
2050
|
+
if (!await this.isHeadword(lower)) {
|
|
2051
|
+
const root = await this.findRoot(base);
|
|
2052
|
+
const isVerb = root && (root.endsWith("mek") || root.endsWith("mak")) || VERB_CONJUGATION_REGEX.test(base);
|
|
2053
|
+
if (isVerb) {
|
|
2054
|
+
const correctEnding = ending.startsWith("t") ? ending === "te" ? "de" : "da" : ending;
|
|
2055
|
+
issues.push({
|
|
2056
|
+
type: "conjunction_da",
|
|
2057
|
+
word: rawWord,
|
|
2058
|
+
startIndex,
|
|
2059
|
+
endIndex,
|
|
2060
|
+
suggestion: `${base} ${correctEnding}`,
|
|
2061
|
+
message: `'da/de' ba\u011Flac\u0131 fiillerden sonra her zaman ayr\u0131 yaz\u0131l\u0131r (ba\u011Fla\xE7 olan da/de sertle\u015Fmez).`
|
|
2062
|
+
});
|
|
2063
|
+
flagged = true;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
if (!flagged) {
|
|
2068
|
+
const check = await this.checkSpelling(rawWord);
|
|
2069
|
+
if (!check.isCorrect) {
|
|
2070
|
+
issues.push({
|
|
2071
|
+
type: "spelling",
|
|
2072
|
+
word: rawWord,
|
|
2073
|
+
startIndex,
|
|
2074
|
+
endIndex,
|
|
2075
|
+
suggestion: check.suggestion,
|
|
2076
|
+
message: check.suggestion ? `'${rawWord}' yanl\u0131\u015F yaz\u0131lm\u0131\u015F olabilir. \xD6neri: '${check.suggestion}'` : `'${rawWord}' s\xF6zl\xFCkte bulunamad\u0131.`
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
return {
|
|
2082
|
+
text,
|
|
2083
|
+
issues,
|
|
2084
|
+
isCorrect: issues.length === 0
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
};
|
|
2088
|
+
var TDKClient = class {
|
|
2089
|
+
constructor(config) {
|
|
2090
|
+
if (config) {
|
|
2091
|
+
TDK.configure(config);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
getWord(word) {
|
|
2095
|
+
return TDK.getWord(word);
|
|
2096
|
+
}
|
|
2097
|
+
getMeanings(word) {
|
|
2098
|
+
return TDK.getMeanings(word);
|
|
2099
|
+
}
|
|
2100
|
+
checkSpelling(word) {
|
|
2101
|
+
return TDK.checkSpelling(word);
|
|
2102
|
+
}
|
|
2103
|
+
findRoot(word) {
|
|
2104
|
+
return TDK.findRoot(word);
|
|
2105
|
+
}
|
|
2106
|
+
stem(word) {
|
|
2107
|
+
return TDK.stem(word);
|
|
2108
|
+
}
|
|
2109
|
+
proofread(text) {
|
|
2110
|
+
return TDK.proofread(text);
|
|
2111
|
+
}
|
|
2112
|
+
patternSearch(pattern, options) {
|
|
2113
|
+
return TDK.patternSearch(pattern, options);
|
|
2114
|
+
}
|
|
2115
|
+
findAnagrams(letters, options) {
|
|
2116
|
+
return TDK.findAnagrams(letters, options);
|
|
2117
|
+
}
|
|
2118
|
+
findRhymes(word, options) {
|
|
2119
|
+
return TDK.findRhymes(word, options);
|
|
2120
|
+
}
|
|
2121
|
+
syllabicate(word) {
|
|
2122
|
+
return TDK.syllabicate(word);
|
|
2123
|
+
}
|
|
2124
|
+
checkVowelHarmony(word) {
|
|
2125
|
+
return TDK.checkVowelHarmony(word);
|
|
2126
|
+
}
|
|
2127
|
+
checkLabialHarmony(word) {
|
|
2128
|
+
return TDK.checkLabialHarmony(word);
|
|
2129
|
+
}
|
|
1105
2130
|
};
|
|
1106
2131
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1107
2132
|
0 && (module.exports = {
|
|
1108
2133
|
TDK,
|
|
2134
|
+
TDKClient,
|
|
1109
2135
|
TDKError,
|
|
1110
2136
|
TDKNetworkError,
|
|
1111
|
-
TDKValidationError
|
|
2137
|
+
TDKValidationError,
|
|
2138
|
+
TURKISH_SUFFIXES,
|
|
2139
|
+
TURKISH_VOWELS,
|
|
2140
|
+
getStemCandidates,
|
|
2141
|
+
isVowel,
|
|
2142
|
+
restoreConsonantSoftening,
|
|
2143
|
+
restoreGemination,
|
|
2144
|
+
restoreInfinitive,
|
|
2145
|
+
restoreVowelDrop,
|
|
2146
|
+
restoreVowelNarrowing
|
|
1112
2147
|
});
|