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/cli.js
CHANGED
|
@@ -50,6 +50,559 @@ var TDKNetworkError = class extends TDKError {
|
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
// src/morphology.ts
|
|
54
|
+
var TURKISH_VOWELS = "ae\u0131io\xF6u\xFC";
|
|
55
|
+
function isVowel(ch) {
|
|
56
|
+
return TURKISH_VOWELS.includes(ch);
|
|
57
|
+
}
|
|
58
|
+
var TURKISH_SUFFIXES = [
|
|
59
|
+
// 9-letter composite suffixes
|
|
60
|
+
"lerimizden",
|
|
61
|
+
"lar\u0131m\u0131zdan",
|
|
62
|
+
"lerinizden",
|
|
63
|
+
"lar\u0131n\u0131zdan",
|
|
64
|
+
// 8-letter composite suffixes
|
|
65
|
+
"lerinin",
|
|
66
|
+
"lar\u0131n\u0131n",
|
|
67
|
+
"lerinde",
|
|
68
|
+
"lar\u0131nda",
|
|
69
|
+
"lerinden",
|
|
70
|
+
"lar\u0131ndan",
|
|
71
|
+
"leriyle",
|
|
72
|
+
"lar\u0131yla",
|
|
73
|
+
"lerini",
|
|
74
|
+
"lar\u0131n\u0131",
|
|
75
|
+
"lerimize",
|
|
76
|
+
"lar\u0131m\u0131za",
|
|
77
|
+
"lerimizle",
|
|
78
|
+
"lar\u0131m\u0131zla",
|
|
79
|
+
"lerinizin",
|
|
80
|
+
"lar\u0131n\u0131z\u0131n",
|
|
81
|
+
"lerinizde",
|
|
82
|
+
"lar\u0131n\u0131zda",
|
|
83
|
+
"d\u0131\u011F\u0131ndan",
|
|
84
|
+
"di\u011Finden",
|
|
85
|
+
"du\u011Fundan",
|
|
86
|
+
"d\xFC\u011F\xFCnden",
|
|
87
|
+
"t\u0131\u011F\u0131ndan",
|
|
88
|
+
"ti\u011Finden",
|
|
89
|
+
"tu\u011Fundan",
|
|
90
|
+
"t\xFC\u011F\xFCnden",
|
|
91
|
+
// 7-letter composite suffixes
|
|
92
|
+
"ecektir",
|
|
93
|
+
"acakt\u0131r",
|
|
94
|
+
"ece\u011Fim",
|
|
95
|
+
"aca\u011F\u0131m",
|
|
96
|
+
"eceksin",
|
|
97
|
+
"acaks\u0131n",
|
|
98
|
+
"ece\u011Fiz",
|
|
99
|
+
"aca\u011F\u0131z",
|
|
100
|
+
"lerimiz",
|
|
101
|
+
"lar\u0131m\u0131z",
|
|
102
|
+
"leriniz",
|
|
103
|
+
"lar\u0131n\u0131z",
|
|
104
|
+
"umuzdan",
|
|
105
|
+
"\xFCm\xFCzden",
|
|
106
|
+
"inizden",
|
|
107
|
+
"\u0131n\u0131zdan",
|
|
108
|
+
"\xFCn\xFCzden",
|
|
109
|
+
"d\u0131\u011F\u0131nda",
|
|
110
|
+
"di\u011Finde",
|
|
111
|
+
"du\u011Funda",
|
|
112
|
+
"d\xFC\u011F\xFCnde",
|
|
113
|
+
"t\u0131\u011F\u0131nda",
|
|
114
|
+
"ti\u011Finde",
|
|
115
|
+
"tu\u011Funda",
|
|
116
|
+
"t\xFC\u011F\xFCnde",
|
|
117
|
+
"mas\u0131na",
|
|
118
|
+
"mesine",
|
|
119
|
+
"\u0131yorsunuz",
|
|
120
|
+
"iyorsunuz",
|
|
121
|
+
"uyorsunuz",
|
|
122
|
+
"\xFCyorsunuz",
|
|
123
|
+
"yorsunuz",
|
|
124
|
+
// 6-letter composite suffixes
|
|
125
|
+
"iyorsa",
|
|
126
|
+
"iyorduk",
|
|
127
|
+
"iyordu",
|
|
128
|
+
"iyormu\u015F",
|
|
129
|
+
"\u0131yorsa",
|
|
130
|
+
"\u0131yorduk",
|
|
131
|
+
"\u0131yordu",
|
|
132
|
+
"\u0131yormu\u015F",
|
|
133
|
+
"uyorsa",
|
|
134
|
+
"uyorduk",
|
|
135
|
+
"uyordu",
|
|
136
|
+
"uyormu\u015F",
|
|
137
|
+
"\xFCyorsa",
|
|
138
|
+
"\xFCyorduk",
|
|
139
|
+
"\xFCyordu",
|
|
140
|
+
"\xFCyormu\u015F",
|
|
141
|
+
"\u0131yorsun",
|
|
142
|
+
"iyorsun",
|
|
143
|
+
"uyorsun",
|
|
144
|
+
"\xFCyorsun",
|
|
145
|
+
"\u0131yorlar",
|
|
146
|
+
"iyorlar",
|
|
147
|
+
"uyorlar",
|
|
148
|
+
"\xFCyorlar",
|
|
149
|
+
"iyoruz",
|
|
150
|
+
"\u0131yoruz",
|
|
151
|
+
"uyoruz",
|
|
152
|
+
"\xFCyoruz",
|
|
153
|
+
"imizin",
|
|
154
|
+
"\u0131m\u0131z\u0131n",
|
|
155
|
+
"umuzun",
|
|
156
|
+
"\xFCm\xFCz\xFCn",
|
|
157
|
+
"imizde",
|
|
158
|
+
"\u0131m\u0131zda",
|
|
159
|
+
"umuzda",
|
|
160
|
+
"\xFCm\xFCzde",
|
|
161
|
+
"imizden",
|
|
162
|
+
"\u0131m\u0131zdan",
|
|
163
|
+
"imizle",
|
|
164
|
+
"\u0131m\u0131zla",
|
|
165
|
+
"umuzla",
|
|
166
|
+
"\xFCm\xFCzle",
|
|
167
|
+
"lerdir",
|
|
168
|
+
"lard\u0131r",
|
|
169
|
+
"mu\u015Ftur",
|
|
170
|
+
"mi\u015Ftir",
|
|
171
|
+
"mu\u015Ftur",
|
|
172
|
+
"m\xFC\u015Ft\xFCr",
|
|
173
|
+
"lerden",
|
|
174
|
+
"lardan",
|
|
175
|
+
"lerine",
|
|
176
|
+
"lar\u0131na",
|
|
177
|
+
"leriyle",
|
|
178
|
+
"lar\u0131yla",
|
|
179
|
+
"seniz",
|
|
180
|
+
"san\u0131z",
|
|
181
|
+
"diniz",
|
|
182
|
+
"d\u0131n\u0131z",
|
|
183
|
+
"dunuz",
|
|
184
|
+
"d\xFCn\xFCz",
|
|
185
|
+
"tiniz",
|
|
186
|
+
"t\u0131n\u0131z",
|
|
187
|
+
"tunuz",
|
|
188
|
+
"t\xFCn\xFCz",
|
|
189
|
+
"siniz",
|
|
190
|
+
"s\u0131n\u0131z",
|
|
191
|
+
"sunuz",
|
|
192
|
+
"s\xFCn\xFCz",
|
|
193
|
+
"d\u0131k\xE7a",
|
|
194
|
+
"dik\xE7e",
|
|
195
|
+
"duk\xE7a",
|
|
196
|
+
"d\xFCk\xE7e",
|
|
197
|
+
"t\u0131k\xE7a",
|
|
198
|
+
"tik\xE7e",
|
|
199
|
+
"tuk\xE7a",
|
|
200
|
+
"t\xFCk\xE7e",
|
|
201
|
+
"\u0131rken",
|
|
202
|
+
"irken",
|
|
203
|
+
"urken",
|
|
204
|
+
"\xFCrken",
|
|
205
|
+
"arken",
|
|
206
|
+
"erken",
|
|
207
|
+
// 5-letter suffixes
|
|
208
|
+
"lerde",
|
|
209
|
+
"larda",
|
|
210
|
+
"lerle",
|
|
211
|
+
"larla",
|
|
212
|
+
"lerin",
|
|
213
|
+
"lar\u0131n",
|
|
214
|
+
"lerim",
|
|
215
|
+
"lar\u0131m",
|
|
216
|
+
"dirler",
|
|
217
|
+
"d\u0131rlar",
|
|
218
|
+
"d\xFCrler",
|
|
219
|
+
"durlar",
|
|
220
|
+
"tirler",
|
|
221
|
+
"t\u0131rlar",
|
|
222
|
+
"t\xFCrler",
|
|
223
|
+
"turlar",
|
|
224
|
+
"siniz",
|
|
225
|
+
"s\u0131n\u0131z",
|
|
226
|
+
"sunuz",
|
|
227
|
+
"s\xFCn\xFCz",
|
|
228
|
+
"yorum",
|
|
229
|
+
"yorsun",
|
|
230
|
+
"uyoruz",
|
|
231
|
+
"yorsunuz",
|
|
232
|
+
"yorlar",
|
|
233
|
+
"eceks",
|
|
234
|
+
"acaks",
|
|
235
|
+
"eyim",
|
|
236
|
+
"ay\u0131m",
|
|
237
|
+
"indik",
|
|
238
|
+
"\u0131nd\u0131k",
|
|
239
|
+
"unduk",
|
|
240
|
+
"\xFCnd\xFCk",
|
|
241
|
+
"ildik",
|
|
242
|
+
"\u0131ld\u0131k",
|
|
243
|
+
"ulduk",
|
|
244
|
+
"\xFCld\xFCk",
|
|
245
|
+
"meden",
|
|
246
|
+
"madan",
|
|
247
|
+
"y\u0131n\u0131z",
|
|
248
|
+
"yiniz",
|
|
249
|
+
"yunuz",
|
|
250
|
+
"y\xFCn\xFCz",
|
|
251
|
+
// 4-letter suffixes
|
|
252
|
+
"imiz",
|
|
253
|
+
"\u0131m\u0131z",
|
|
254
|
+
"umuz",
|
|
255
|
+
"\xFCm\xFCz",
|
|
256
|
+
"iniz",
|
|
257
|
+
"\u0131n\u0131z",
|
|
258
|
+
"unuz",
|
|
259
|
+
"\xFCn\xFCz",
|
|
260
|
+
"leri",
|
|
261
|
+
"lar\u0131",
|
|
262
|
+
"idir",
|
|
263
|
+
"\u0131d\u0131r",
|
|
264
|
+
"udur",
|
|
265
|
+
"\xFCd\xFCr",
|
|
266
|
+
"ecek",
|
|
267
|
+
"acak",
|
|
268
|
+
"erek",
|
|
269
|
+
"arak",
|
|
270
|
+
"ince",
|
|
271
|
+
"\u0131nca",
|
|
272
|
+
"unca",
|
|
273
|
+
"\xFCnce",
|
|
274
|
+
"ken",
|
|
275
|
+
"meli",
|
|
276
|
+
"mal\u0131",
|
|
277
|
+
"iyor",
|
|
278
|
+
"\u0131yor",
|
|
279
|
+
"uyor",
|
|
280
|
+
"\xFCyor",
|
|
281
|
+
"mi\u015Fti",
|
|
282
|
+
"m\u0131\u015Ft\u0131",
|
|
283
|
+
"mu\u015Ftu",
|
|
284
|
+
"m\xFC\u015Ft\xFC",
|
|
285
|
+
"seydi",
|
|
286
|
+
"sayd\u0131",
|
|
287
|
+
"ydim",
|
|
288
|
+
"yd\u0131m",
|
|
289
|
+
"ydum",
|
|
290
|
+
"yd\xFCm",
|
|
291
|
+
"tiler",
|
|
292
|
+
"t\u0131lar",
|
|
293
|
+
"diler",
|
|
294
|
+
"d\u0131lar",
|
|
295
|
+
"ikten",
|
|
296
|
+
"\u0131ktan",
|
|
297
|
+
"uktan",
|
|
298
|
+
"\xFCkten",
|
|
299
|
+
// 3-letter suffixes
|
|
300
|
+
"ler",
|
|
301
|
+
"lar",
|
|
302
|
+
"den",
|
|
303
|
+
"dan",
|
|
304
|
+
"ten",
|
|
305
|
+
"tan",
|
|
306
|
+
"dir",
|
|
307
|
+
"d\u0131r",
|
|
308
|
+
"dur",
|
|
309
|
+
"d\xFCr",
|
|
310
|
+
"tir",
|
|
311
|
+
"t\u0131r",
|
|
312
|
+
"tur",
|
|
313
|
+
"t\xFCr",
|
|
314
|
+
"nin",
|
|
315
|
+
"n\u0131n",
|
|
316
|
+
"nun",
|
|
317
|
+
"n\xFCn",
|
|
318
|
+
"yle",
|
|
319
|
+
"yla",
|
|
320
|
+
"mi\u015F",
|
|
321
|
+
"m\u0131\u015F",
|
|
322
|
+
"mu\u015F",
|
|
323
|
+
"m\xFC\u015F",
|
|
324
|
+
"dim",
|
|
325
|
+
"d\u0131m",
|
|
326
|
+
"dum",
|
|
327
|
+
"d\xFCm",
|
|
328
|
+
"tim",
|
|
329
|
+
"t\u0131m",
|
|
330
|
+
"tum",
|
|
331
|
+
"t\xFCm",
|
|
332
|
+
"din",
|
|
333
|
+
"d\u0131n",
|
|
334
|
+
"dun",
|
|
335
|
+
"d\xFCn",
|
|
336
|
+
"tin",
|
|
337
|
+
"t\u0131n",
|
|
338
|
+
"tun",
|
|
339
|
+
"t\xFCn",
|
|
340
|
+
"dik",
|
|
341
|
+
"d\u0131k",
|
|
342
|
+
"duk",
|
|
343
|
+
"d\xFCk",
|
|
344
|
+
"tik",
|
|
345
|
+
"t\u0131k",
|
|
346
|
+
"tuk",
|
|
347
|
+
"t\xFCk",
|
|
348
|
+
"ydi",
|
|
349
|
+
"yd\u0131",
|
|
350
|
+
"ydu",
|
|
351
|
+
"yd\xFC",
|
|
352
|
+
"yim",
|
|
353
|
+
"y\u0131m",
|
|
354
|
+
"yum",
|
|
355
|
+
"y\xFCm",
|
|
356
|
+
"sin",
|
|
357
|
+
"s\u0131n",
|
|
358
|
+
"sun",
|
|
359
|
+
"s\xFCn",
|
|
360
|
+
"sen",
|
|
361
|
+
"san",
|
|
362
|
+
"sem",
|
|
363
|
+
"sam",
|
|
364
|
+
"sek",
|
|
365
|
+
"sak",
|
|
366
|
+
"siz",
|
|
367
|
+
"s\u0131z",
|
|
368
|
+
"suz",
|
|
369
|
+
"s\xFCz",
|
|
370
|
+
"lik",
|
|
371
|
+
"l\u0131k",
|
|
372
|
+
"luk",
|
|
373
|
+
"l\xFCk",
|
|
374
|
+
"ici",
|
|
375
|
+
"\u0131c\u0131",
|
|
376
|
+
"ucu",
|
|
377
|
+
"\xFCc\xFC",
|
|
378
|
+
"gen",
|
|
379
|
+
"gan",
|
|
380
|
+
"ken",
|
|
381
|
+
"kan",
|
|
382
|
+
"len",
|
|
383
|
+
"lan",
|
|
384
|
+
"le\u015F",
|
|
385
|
+
"la\u015F",
|
|
386
|
+
"mek",
|
|
387
|
+
"mak",
|
|
388
|
+
"yor",
|
|
389
|
+
// 2-letter suffixes
|
|
390
|
+
"de",
|
|
391
|
+
"da",
|
|
392
|
+
"te",
|
|
393
|
+
"ta",
|
|
394
|
+
"im",
|
|
395
|
+
"\u0131m",
|
|
396
|
+
"um",
|
|
397
|
+
"\xFCm",
|
|
398
|
+
"in",
|
|
399
|
+
"\u0131n",
|
|
400
|
+
"un",
|
|
401
|
+
"\xFCn",
|
|
402
|
+
"iz",
|
|
403
|
+
"\u0131z",
|
|
404
|
+
"uz",
|
|
405
|
+
"\xFCz",
|
|
406
|
+
"si",
|
|
407
|
+
"s\u0131",
|
|
408
|
+
"su",
|
|
409
|
+
"s\xFC",
|
|
410
|
+
"ye",
|
|
411
|
+
"ya",
|
|
412
|
+
"le",
|
|
413
|
+
"la",
|
|
414
|
+
"di",
|
|
415
|
+
"d\u0131",
|
|
416
|
+
"du",
|
|
417
|
+
"d\xFC",
|
|
418
|
+
"ti",
|
|
419
|
+
"t\u0131",
|
|
420
|
+
"tu",
|
|
421
|
+
"t\xFC",
|
|
422
|
+
"se",
|
|
423
|
+
"sa",
|
|
424
|
+
"ce",
|
|
425
|
+
"ca",
|
|
426
|
+
"\xE7e",
|
|
427
|
+
"\xE7a",
|
|
428
|
+
"me",
|
|
429
|
+
"ma",
|
|
430
|
+
"ip",
|
|
431
|
+
"\u0131p",
|
|
432
|
+
"up",
|
|
433
|
+
"\xFCp",
|
|
434
|
+
"en",
|
|
435
|
+
"an",
|
|
436
|
+
"i\u015F",
|
|
437
|
+
"\u0131\u015F",
|
|
438
|
+
"u\u015F",
|
|
439
|
+
"\xFC\u015F",
|
|
440
|
+
"li",
|
|
441
|
+
"l\u0131",
|
|
442
|
+
"lu",
|
|
443
|
+
"l\xFC",
|
|
444
|
+
"ci",
|
|
445
|
+
"c\u0131",
|
|
446
|
+
"cu",
|
|
447
|
+
"c\xFC",
|
|
448
|
+
"\xE7i",
|
|
449
|
+
"\xE7\u0131",
|
|
450
|
+
"\xE7u",
|
|
451
|
+
"\xE7\xFC",
|
|
452
|
+
// 1-letter suffixes (vowels / basic case endings)
|
|
453
|
+
"e",
|
|
454
|
+
"a",
|
|
455
|
+
"i",
|
|
456
|
+
"\u0131",
|
|
457
|
+
"u",
|
|
458
|
+
"\xFC"
|
|
459
|
+
];
|
|
460
|
+
function restoreConsonantSoftening(stem) {
|
|
461
|
+
if (stem.length < 2)
|
|
462
|
+
return [];
|
|
463
|
+
const last = stem.slice(-1);
|
|
464
|
+
const base = stem.slice(0, -1);
|
|
465
|
+
switch (last) {
|
|
466
|
+
case "b":
|
|
467
|
+
return [base + "p"];
|
|
468
|
+
case "c":
|
|
469
|
+
return [base + "\xE7"];
|
|
470
|
+
case "d":
|
|
471
|
+
return [base + "t"];
|
|
472
|
+
case "\u011F":
|
|
473
|
+
return [base + "k"];
|
|
474
|
+
case "g":
|
|
475
|
+
return [base + "k"];
|
|
476
|
+
default:
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function restoreVowelDrop(stem) {
|
|
481
|
+
if (stem.length < 3)
|
|
482
|
+
return [];
|
|
483
|
+
const c1 = stem[stem.length - 2];
|
|
484
|
+
const c2 = stem[stem.length - 1];
|
|
485
|
+
if (!isVowel(c1) && !isVowel(c2)) {
|
|
486
|
+
const vowelsInBase = stem.slice(0, -2).split("").filter(isVowel);
|
|
487
|
+
if (vowelsInBase.length > 0) {
|
|
488
|
+
const lastVowel = vowelsInBase[vowelsInBase.length - 1];
|
|
489
|
+
let inserted = "i";
|
|
490
|
+
if ("a\u0131".includes(lastVowel))
|
|
491
|
+
inserted = "\u0131";
|
|
492
|
+
else if ("ei".includes(lastVowel))
|
|
493
|
+
inserted = "i";
|
|
494
|
+
else if ("ou".includes(lastVowel))
|
|
495
|
+
inserted = "u";
|
|
496
|
+
else if ("\xF6\xFC".includes(lastVowel))
|
|
497
|
+
inserted = "\xFC";
|
|
498
|
+
return [stem.slice(0, -1) + inserted + c2];
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return [];
|
|
502
|
+
}
|
|
503
|
+
function restoreGemination(stem) {
|
|
504
|
+
if (stem.length < 3)
|
|
505
|
+
return [];
|
|
506
|
+
const c1 = stem[stem.length - 2];
|
|
507
|
+
const c2 = stem[stem.length - 1];
|
|
508
|
+
if (c1 === c2 && !isVowel(c1)) {
|
|
509
|
+
const single = stem.slice(0, -1);
|
|
510
|
+
const hardened = restoreConsonantSoftening(single);
|
|
511
|
+
return [single, ...hardened];
|
|
512
|
+
}
|
|
513
|
+
return [];
|
|
514
|
+
}
|
|
515
|
+
function restoreVowelNarrowing(stem) {
|
|
516
|
+
if (stem.length < 2)
|
|
517
|
+
return [];
|
|
518
|
+
if (stem === "di")
|
|
519
|
+
return ["de"];
|
|
520
|
+
if (stem === "yi")
|
|
521
|
+
return ["ye"];
|
|
522
|
+
const lastChar = stem[stem.length - 1];
|
|
523
|
+
const isLastNarrow = "\u0131iu\xFC".includes(lastChar);
|
|
524
|
+
if (isLastNarrow) {
|
|
525
|
+
const vowelsInBase = stem.slice(0, -1).split("").filter(isVowel);
|
|
526
|
+
const lastVowel = vowelsInBase.length > 0 ? vowelsInBase[vowelsInBase.length - 1] : lastChar;
|
|
527
|
+
const widened = "a\u0131ou".includes(lastVowel) ? "a" : "e";
|
|
528
|
+
return [stem.slice(0, -1) + widened];
|
|
529
|
+
}
|
|
530
|
+
if (!isVowel(lastChar)) {
|
|
531
|
+
const vowelsInBase = stem.split("").filter(isVowel);
|
|
532
|
+
if (vowelsInBase.length > 0) {
|
|
533
|
+
const lastVowel = vowelsInBase[vowelsInBase.length - 1];
|
|
534
|
+
const widened = "a\u0131ou".includes(lastVowel) ? "a" : "e";
|
|
535
|
+
return [stem + widened];
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return [];
|
|
539
|
+
}
|
|
540
|
+
function restoreInfinitive(stem) {
|
|
541
|
+
if (stem.length < 2)
|
|
542
|
+
return [];
|
|
543
|
+
const vowelsInBase = stem.split("").filter(isVowel);
|
|
544
|
+
if (vowelsInBase.length === 0)
|
|
545
|
+
return [];
|
|
546
|
+
const lastVowel = vowelsInBase[vowelsInBase.length - 1];
|
|
547
|
+
return "a\u0131ou".includes(lastVowel) ? [stem + "mak"] : [stem + "mek"];
|
|
548
|
+
}
|
|
549
|
+
function getStemCandidates(word2, minStemLength = 2, maxDepth = 4) {
|
|
550
|
+
if (!word2 || word2.trim().length === 0)
|
|
551
|
+
return [];
|
|
552
|
+
const raw = word2.trim();
|
|
553
|
+
const normalized = raw.toLocaleLowerCase("tr-TR");
|
|
554
|
+
const candidatesWithWeight = [];
|
|
555
|
+
const seen = /* @__PURE__ */ new Set();
|
|
556
|
+
if (raw.includes("'") || raw.includes("\u2019")) {
|
|
557
|
+
const apostropheStem = normalized.split(/['’]/)[0];
|
|
558
|
+
if (apostropheStem.length >= minStemLength) {
|
|
559
|
+
candidatesWithWeight.push({ candidate: apostropheStem, baseLength: apostropheStem.length + 10 });
|
|
560
|
+
seen.add(apostropheStem);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
let frontier = [normalized];
|
|
564
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
565
|
+
const nextFrontier = [];
|
|
566
|
+
for (const current of frontier) {
|
|
567
|
+
for (const suffix of TURKISH_SUFFIXES) {
|
|
568
|
+
if (current.length - suffix.length >= minStemLength && current.endsWith(suffix)) {
|
|
569
|
+
const stem = current.slice(0, -suffix.length);
|
|
570
|
+
const hardened = restoreConsonantSoftening(stem);
|
|
571
|
+
const vowelDropped = restoreVowelDrop(stem);
|
|
572
|
+
const geminated = restoreGemination(stem);
|
|
573
|
+
const isNarrowingSuffix = suffix.startsWith("yor") || suffix.includes("iyor") || suffix.includes("\u0131yor") || suffix.includes("uyor") || suffix.includes("\xFCyor");
|
|
574
|
+
const isDeYeBuffer = (stem === "di" || stem === "yi") && suffix.startsWith("y");
|
|
575
|
+
const narrowed = isNarrowingSuffix || isDeYeBuffer ? restoreVowelNarrowing(stem) : [];
|
|
576
|
+
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");
|
|
577
|
+
const verbalBases = [stem, ...hardened, ...narrowed];
|
|
578
|
+
const infinitives = verbalBases.flatMap((v) => restoreInfinitive(v));
|
|
579
|
+
const variants = [stem, ...hardened, ...vowelDropped, ...geminated, ...narrowed];
|
|
580
|
+
for (const variant of variants) {
|
|
581
|
+
if (!seen.has(variant) && variant !== normalized) {
|
|
582
|
+
seen.add(variant);
|
|
583
|
+
nextFrontier.push(variant);
|
|
584
|
+
candidatesWithWeight.push({ candidate: variant, baseLength: stem.length });
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
for (const inf of infinitives) {
|
|
588
|
+
if (!seen.has(inf) && inf !== normalized) {
|
|
589
|
+
seen.add(inf);
|
|
590
|
+
nextFrontier.push(inf);
|
|
591
|
+
const weight = isVerbSuffix ? stem.length + 5 : stem.length;
|
|
592
|
+
candidatesWithWeight.push({ candidate: inf, baseLength: weight });
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (nextFrontier.length === 0)
|
|
599
|
+
break;
|
|
600
|
+
frontier = nextFrontier;
|
|
601
|
+
}
|
|
602
|
+
candidatesWithWeight.sort((a, b) => b.baseLength - a.baseLength);
|
|
603
|
+
return [...new Set(candidatesWithWeight.map((c2) => c2.candidate))];
|
|
604
|
+
}
|
|
605
|
+
|
|
53
606
|
// src/tdk.ts
|
|
54
607
|
var fs = __toESM(require("fs"));
|
|
55
608
|
var path = __toESM(require("path"));
|
|
@@ -136,11 +689,30 @@ M71DMi+y1+TRSJVClEMwvA4yL++7q9XZx5r5wBRWB4kQTKH5qyoZnDw7iiuh1lID
|
|
|
136
689
|
yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
137
690
|
-----END CERTIFICATE-----`
|
|
138
691
|
];
|
|
692
|
+
// Configuration
|
|
693
|
+
static defaultTimeoutMs = 8e3;
|
|
694
|
+
static defaultRetries = 1;
|
|
695
|
+
static maxCacheSize = 1e3;
|
|
139
696
|
// Cache Mechanism
|
|
140
697
|
static isCacheEnabled = false;
|
|
141
698
|
static wordCache = /* @__PURE__ */ new Map();
|
|
142
699
|
static dailyContentCache = null;
|
|
143
700
|
static autocompleteCache = [];
|
|
701
|
+
static autocompleteSet = /* @__PURE__ */ new Set();
|
|
702
|
+
static stemCache = /* @__PURE__ */ new Map();
|
|
703
|
+
/**
|
|
704
|
+
* Configures global client options such as network timeout, retries, and cache size.
|
|
705
|
+
*/
|
|
706
|
+
static configure(config) {
|
|
707
|
+
if (config.timeoutMs !== void 0)
|
|
708
|
+
this.defaultTimeoutMs = Math.max(100, config.timeoutMs);
|
|
709
|
+
if (config.retries !== void 0)
|
|
710
|
+
this.defaultRetries = Math.max(0, config.retries);
|
|
711
|
+
if (config.cache !== void 0)
|
|
712
|
+
this.enableCache(config.cache);
|
|
713
|
+
if (config.maxCacheSize !== void 0)
|
|
714
|
+
this.maxCacheSize = Math.max(10, config.maxCacheSize);
|
|
715
|
+
}
|
|
144
716
|
/**
|
|
145
717
|
* Enables or disables in-memory caching for API requests.
|
|
146
718
|
*/
|
|
@@ -157,10 +729,53 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
157
729
|
this.wordCache.clear();
|
|
158
730
|
this.dailyContentCache = null;
|
|
159
731
|
this.autocompleteCache = [];
|
|
732
|
+
this.autocompleteSet.clear();
|
|
733
|
+
this.stemCache.clear();
|
|
734
|
+
}
|
|
735
|
+
static setBoundedCache(map, key, value) {
|
|
736
|
+
if (map.size >= this.maxCacheSize) {
|
|
737
|
+
const firstKey = map.keys().next().value;
|
|
738
|
+
if (firstKey !== void 0)
|
|
739
|
+
map.delete(firstKey);
|
|
740
|
+
}
|
|
741
|
+
map.set(key, value);
|
|
160
742
|
}
|
|
161
743
|
static delay(ms) {
|
|
162
744
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
163
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* Internal helper that performs HTTP fetch with timeout and automatic retry on network/5xx errors.
|
|
748
|
+
*/
|
|
749
|
+
static async fetchWithRetry(url, options = {}, retries = this.defaultRetries, timeoutMs = this.defaultTimeoutMs) {
|
|
750
|
+
let lastError;
|
|
751
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
752
|
+
try {
|
|
753
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
754
|
+
const headers = {
|
|
755
|
+
"User-Agent": "TDK-API-Nodejs-Wrapper/1.0",
|
|
756
|
+
...options.headers || {}
|
|
757
|
+
};
|
|
758
|
+
const res = await fetch(url, { ...options, headers, signal });
|
|
759
|
+
if (res.ok || res.status >= 400 && res.status < 500) {
|
|
760
|
+
return res;
|
|
761
|
+
}
|
|
762
|
+
if (attempt < retries) {
|
|
763
|
+
await this.delay(200 * (attempt + 1));
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
return res;
|
|
767
|
+
} catch (err) {
|
|
768
|
+
lastError = err;
|
|
769
|
+
if (attempt < retries) {
|
|
770
|
+
await this.delay(200 * (attempt + 1));
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
throw new TDKNetworkError(`Request to ${url} failed after ${retries + 1} attempts.`, {
|
|
776
|
+
cause: lastError
|
|
777
|
+
});
|
|
778
|
+
}
|
|
164
779
|
/**
|
|
165
780
|
* Fetches detailed information for a given word from the TDK Dictionary.
|
|
166
781
|
*/
|
|
@@ -175,9 +790,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
175
790
|
const url = `${this.BASE_URL}/gts?ara=${encodeURIComponent(cleanWord)}`;
|
|
176
791
|
let response;
|
|
177
792
|
try {
|
|
178
|
-
response = await
|
|
179
|
-
headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" }
|
|
180
|
-
});
|
|
793
|
+
response = await this.fetchWithRetry(url);
|
|
181
794
|
} catch (error) {
|
|
182
795
|
throw new TDKNetworkError("Failed to fetch word from TDK: request failed.", { cause: error });
|
|
183
796
|
}
|
|
@@ -194,12 +807,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
194
807
|
}
|
|
195
808
|
if (!Array.isArray(data) && data && "error" in data) {
|
|
196
809
|
if (this.isCacheEnabled)
|
|
197
|
-
this.wordCache
|
|
810
|
+
this.setBoundedCache(this.wordCache, cleanWord, []);
|
|
198
811
|
return [];
|
|
199
812
|
}
|
|
200
813
|
const results = data;
|
|
201
814
|
if (this.isCacheEnabled) {
|
|
202
|
-
this.wordCache
|
|
815
|
+
this.setBoundedCache(this.wordCache, cleanWord, results);
|
|
203
816
|
}
|
|
204
817
|
return results;
|
|
205
818
|
}
|
|
@@ -263,6 +876,17 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
263
876
|
return [];
|
|
264
877
|
}
|
|
265
878
|
}
|
|
879
|
+
/**
|
|
880
|
+
* Ensures TDK's ~81k headword list is loaded in memory for fast O(1) set operations.
|
|
881
|
+
*/
|
|
882
|
+
static async ensureAutocompleteLoaded() {
|
|
883
|
+
if (this.autocompleteCache.length === 0) {
|
|
884
|
+
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
885
|
+
this.autocompleteSet = new Set(
|
|
886
|
+
this.autocompleteCache.map((w) => w.toLocaleLowerCase("tr-TR"))
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
266
890
|
/**
|
|
267
891
|
* Returns autocomplete suggestions for a given prefix, searched over TDK's
|
|
268
892
|
* full headword list (see `fetchAutocompleteData`). The list is fetched
|
|
@@ -272,12 +896,81 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
272
896
|
static async getSuggestions(prefix) {
|
|
273
897
|
if (!prefix || prefix.trim() === "")
|
|
274
898
|
return [];
|
|
275
|
-
|
|
276
|
-
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
277
|
-
}
|
|
899
|
+
await this.ensureAutocompleteLoaded();
|
|
278
900
|
const cleanPrefix = prefix.trim().toLocaleLowerCase("tr-TR");
|
|
279
901
|
return this.autocompleteCache.filter((w) => w.toLocaleLowerCase("tr-TR").startsWith(cleanPrefix)).slice(0, 10);
|
|
280
902
|
}
|
|
903
|
+
/**
|
|
904
|
+
* Checks whether a word exists as a known headword in TDK dictionary.
|
|
905
|
+
* Checks in-memory autocompleteSet (81k headwords) if loaded, or queries TDK API.
|
|
906
|
+
*/
|
|
907
|
+
static async isHeadword(word2) {
|
|
908
|
+
if (!word2 || word2.trim() === "")
|
|
909
|
+
return false;
|
|
910
|
+
const clean = word2.trim().toLocaleLowerCase("tr-TR");
|
|
911
|
+
await this.ensureAutocompleteLoaded();
|
|
912
|
+
if (this.autocompleteSet.size > 0) {
|
|
913
|
+
return this.autocompleteSet.has(clean);
|
|
914
|
+
}
|
|
915
|
+
try {
|
|
916
|
+
const results = await this.getWord(clean);
|
|
917
|
+
return results.length > 0;
|
|
918
|
+
} catch {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Generates candidate roots for a given Turkish word using progressive BFS suffix stripping,
|
|
924
|
+
* consonant mutation restoration, and vowel drop restoration.
|
|
925
|
+
*/
|
|
926
|
+
static getStemCandidates(word2) {
|
|
927
|
+
return getStemCandidates(word2);
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Finds the dictionary root (headword) of a word by checking direct existence
|
|
931
|
+
* and evaluating candidate stems generated by morphological analysis.
|
|
932
|
+
* Returns the root headword string if found, or null if no match in TDK.
|
|
933
|
+
*/
|
|
934
|
+
static async findRoot(word2) {
|
|
935
|
+
if (!word2 || word2.trim() === "")
|
|
936
|
+
return null;
|
|
937
|
+
const clean = word2.trim().toLocaleLowerCase("tr-TR");
|
|
938
|
+
if (this.stemCache.has(clean)) {
|
|
939
|
+
return this.stemCache.get(clean);
|
|
940
|
+
}
|
|
941
|
+
if (await this.isHeadword(clean)) {
|
|
942
|
+
this.setBoundedCache(this.stemCache, clean, clean);
|
|
943
|
+
return clean;
|
|
944
|
+
}
|
|
945
|
+
const candidates = getStemCandidates(clean);
|
|
946
|
+
for (const candidate of candidates) {
|
|
947
|
+
if (await this.isHeadword(candidate)) {
|
|
948
|
+
this.setBoundedCache(this.stemCache, clean, candidate);
|
|
949
|
+
return candidate;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
this.setBoundedCache(this.stemCache, clean, null);
|
|
953
|
+
return null;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Performs morphological stemming on a Turkish word.
|
|
957
|
+
* Returns a StemResult containing the original word, resolved root, and whether it is inflected.
|
|
958
|
+
*/
|
|
959
|
+
static async stem(word2) {
|
|
960
|
+
if (!word2 || word2.trim() === "")
|
|
961
|
+
return null;
|
|
962
|
+
const clean = word2.trim().toLocaleLowerCase("tr-TR");
|
|
963
|
+
const root = await this.findRoot(word2);
|
|
964
|
+
if (!root) {
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
return {
|
|
968
|
+
word: word2,
|
|
969
|
+
root,
|
|
970
|
+
isInflected: root !== clean,
|
|
971
|
+
candidates: getStemCandidates(word2)
|
|
972
|
+
};
|
|
973
|
+
}
|
|
281
974
|
/**
|
|
282
975
|
* Returns a list of proverbs and idioms containing the word.
|
|
283
976
|
*/
|
|
@@ -494,6 +1187,17 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
494
1187
|
return { isCorrect: false, word: word2, suggestion: mixMatch.dogru };
|
|
495
1188
|
}
|
|
496
1189
|
}
|
|
1190
|
+
const root = await this.findRoot(word2);
|
|
1191
|
+
if (root) {
|
|
1192
|
+
const cleanWord2 = word2.trim().toLocaleLowerCase("tr-TR");
|
|
1193
|
+
const isInflected = root !== cleanWord2;
|
|
1194
|
+
return {
|
|
1195
|
+
isCorrect: true,
|
|
1196
|
+
word: word2,
|
|
1197
|
+
isInflected,
|
|
1198
|
+
root
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
497
1201
|
if (this.autocompleteCache.length === 0) {
|
|
498
1202
|
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
499
1203
|
}
|
|
@@ -921,14 +1625,16 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
921
1625
|
meaningCount: meaningsA.length,
|
|
922
1626
|
origin: originA,
|
|
923
1627
|
syllables: this.syllabicate(a),
|
|
924
|
-
harmony: this.checkVowelHarmony(a)
|
|
1628
|
+
harmony: this.checkVowelHarmony(a),
|
|
1629
|
+
labialHarmony: this.checkLabialHarmony(a)
|
|
925
1630
|
},
|
|
926
1631
|
b: {
|
|
927
1632
|
word: b,
|
|
928
1633
|
meaningCount: meaningsB.length,
|
|
929
1634
|
origin: originB,
|
|
930
1635
|
syllables: this.syllabicate(b),
|
|
931
|
-
harmony: this.checkVowelHarmony(b)
|
|
1636
|
+
harmony: this.checkVowelHarmony(b),
|
|
1637
|
+
labialHarmony: this.checkLabialHarmony(b)
|
|
932
1638
|
}
|
|
933
1639
|
};
|
|
934
1640
|
}
|
|
@@ -990,13 +1696,28 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
990
1696
|
const unique = [...new Set(words)];
|
|
991
1697
|
const analyses = [];
|
|
992
1698
|
for (const word2 of unique) {
|
|
993
|
-
|
|
994
|
-
|
|
1699
|
+
let results = await this.getWord(word2);
|
|
1700
|
+
let found = results.length > 0;
|
|
1701
|
+
let root;
|
|
1702
|
+
let isInflected;
|
|
1703
|
+
if (!found) {
|
|
1704
|
+
const resolvedRoot = await this.findRoot(word2);
|
|
1705
|
+
if (resolvedRoot) {
|
|
1706
|
+
results = await this.getWord(resolvedRoot);
|
|
1707
|
+
if (results.length > 0) {
|
|
1708
|
+
found = true;
|
|
1709
|
+
root = resolvedRoot;
|
|
1710
|
+
isInflected = true;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
995
1714
|
analyses.push({
|
|
996
1715
|
word: word2,
|
|
997
1716
|
found,
|
|
998
1717
|
meaning: found ? this.firstMeaning(results) : null,
|
|
999
|
-
origin: found ? results[0].lisan || "T\xFCrk\xE7e" : null
|
|
1718
|
+
origin: found ? results[0].lisan || "T\xFCrk\xE7e" : null,
|
|
1719
|
+
root,
|
|
1720
|
+
isInflected
|
|
1000
1721
|
});
|
|
1001
1722
|
await this.delay(200);
|
|
1002
1723
|
}
|
|
@@ -1044,9 +1765,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1044
1765
|
}
|
|
1045
1766
|
/**
|
|
1046
1767
|
* Syllabicates a Turkish word based on general grammar rules.
|
|
1768
|
+
* Handles syllable separation for vowels, single consonants, double consonants,
|
|
1769
|
+
* and western loanword three-consonant clusters (e.g. e-lek-trik, kon-trol, or-kes-tra).
|
|
1047
1770
|
*/
|
|
1048
1771
|
static syllabicate(word2) {
|
|
1049
1772
|
const vowels = /[aeıioöuüAEIİOÖUÜ]/;
|
|
1773
|
+
const ONSET_CLUSTERS = /* @__PURE__ */ new Set(["tr", "pr", "kr", "gr", "br", "fr", "dr", "pl", "kl", "fl", "bl", "gl"]);
|
|
1050
1774
|
const result = [];
|
|
1051
1775
|
let currentSyllable = "";
|
|
1052
1776
|
for (let i = word2.length - 1; i >= 0; i--) {
|
|
@@ -1057,8 +1781,13 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1057
1781
|
currentSyllable = word2[i - 1] + currentSyllable;
|
|
1058
1782
|
i--;
|
|
1059
1783
|
} else if (i - 2 >= 0 && !vowels.test(word2[i - 2])) {
|
|
1060
|
-
|
|
1061
|
-
|
|
1784
|
+
if (i - 3 >= 0 && !vowels.test(word2[i - 3]) && ONSET_CLUSTERS.has((word2[i - 2] + word2[i - 1]).toLowerCase())) {
|
|
1785
|
+
currentSyllable = word2[i - 2] + word2[i - 1] + currentSyllable;
|
|
1786
|
+
i -= 2;
|
|
1787
|
+
} else {
|
|
1788
|
+
currentSyllable = word2[i - 1] + currentSyllable;
|
|
1789
|
+
i--;
|
|
1790
|
+
}
|
|
1062
1791
|
}
|
|
1063
1792
|
}
|
|
1064
1793
|
result.unshift(currentSyllable);
|
|
@@ -1088,12 +1817,264 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1088
1817
|
const hasFront = frontVowels.test(lower);
|
|
1089
1818
|
return !(hasBack && hasFront);
|
|
1090
1819
|
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Checks if a word follows Turkish Minor Vowel Harmony (Küçük Ünlü Uyumu / Labial Harmony).
|
|
1822
|
+
* Rules:
|
|
1823
|
+
* 1. After an unrounded vowel (a, e, ı, i), only unrounded vowels (a, e, ı, i) can follow.
|
|
1824
|
+
* 2. After a rounded vowel (o, ö, u, ü), either an unrounded wide (a, e) or rounded narrow (u, ü) vowel can follow.
|
|
1825
|
+
* Single-syllable words and words with <=1 vowel are considered compliant by convention.
|
|
1826
|
+
*/
|
|
1827
|
+
static checkLabialHarmony(word2) {
|
|
1828
|
+
const lower = word2.toLocaleLowerCase("tr-TR");
|
|
1829
|
+
const vowels = lower.split("").filter((ch) => "ae\u0131io\xF6u\xFC".includes(ch));
|
|
1830
|
+
if (vowels.length <= 1)
|
|
1831
|
+
return true;
|
|
1832
|
+
for (let i = 0; i < vowels.length - 1; i++) {
|
|
1833
|
+
const v1 = vowels[i];
|
|
1834
|
+
const v2 = vowels[i + 1];
|
|
1835
|
+
if ("ae\u0131i".includes(v1)) {
|
|
1836
|
+
if (!"ae\u0131i".includes(v2))
|
|
1837
|
+
return false;
|
|
1838
|
+
} else if ("o\xF6u\xFC".includes(v1)) {
|
|
1839
|
+
if (!"aeu\xFC".includes(v2))
|
|
1840
|
+
return false;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
return true;
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Searches TDK headwords using a wildcard / pattern string.
|
|
1847
|
+
* Wildcards:
|
|
1848
|
+
* '_' or '?' matches any single character
|
|
1849
|
+
* '*' matches zero or more characters
|
|
1850
|
+
* Example: "k_l_m" matches "kalem", "kelam", "kilim".
|
|
1851
|
+
* Runs in-memory against TDK's 81k headword list.
|
|
1852
|
+
*/
|
|
1853
|
+
static async patternSearch(pattern, options) {
|
|
1854
|
+
if (!pattern || pattern.trim() === "")
|
|
1855
|
+
return [];
|
|
1856
|
+
await this.ensureAutocompleteLoaded();
|
|
1857
|
+
const cleanPattern = pattern.trim().toLocaleLowerCase("tr-TR");
|
|
1858
|
+
const escaped = cleanPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/[_?]/g, "[\\p{L}]").replace(/\*/g, "[\\p{L}]*");
|
|
1859
|
+
const regex = new RegExp(`^${escaped}$`, "u");
|
|
1860
|
+
const max = options?.maxResults ?? 50;
|
|
1861
|
+
const matches = [];
|
|
1862
|
+
for (const headword of this.autocompleteCache) {
|
|
1863
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
1864
|
+
if (regex.test(lower)) {
|
|
1865
|
+
matches.push(headword);
|
|
1866
|
+
if (matches.length >= max)
|
|
1867
|
+
break;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
return matches;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Finds headwords in TDK that can be formed from the given letters (anagrams).
|
|
1874
|
+
* If exact-length anagrams exist, they are returned.
|
|
1875
|
+
* If none exist (or exactLength is false), valid sub-anagrams (words using a subset of the letters,
|
|
1876
|
+
* minimum 3 letters) are returned, sorted by length descending.
|
|
1877
|
+
*/
|
|
1878
|
+
static async findAnagrams(letters, options) {
|
|
1879
|
+
if (!letters || letters.trim() === "")
|
|
1880
|
+
return [];
|
|
1881
|
+
await this.ensureAutocompleteLoaded();
|
|
1882
|
+
const clean = letters.trim().toLocaleLowerCase("tr-TR").replace(/[^a-zçğıöşüâîû]/gi, "");
|
|
1883
|
+
if (clean.length === 0)
|
|
1884
|
+
return [];
|
|
1885
|
+
const forceExact = options?.exactLength === true;
|
|
1886
|
+
const max = options?.maxResults ?? 50;
|
|
1887
|
+
const getFrequency = (str) => {
|
|
1888
|
+
const freq = {};
|
|
1889
|
+
for (const ch of str) {
|
|
1890
|
+
freq[ch] = (freq[ch] || 0) + 1;
|
|
1891
|
+
}
|
|
1892
|
+
return freq;
|
|
1893
|
+
};
|
|
1894
|
+
const targetFreq = getFrequency(clean);
|
|
1895
|
+
const exactMatches = [];
|
|
1896
|
+
const subMatches = [];
|
|
1897
|
+
for (const headword of this.autocompleteCache) {
|
|
1898
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
1899
|
+
if (lower.includes(" ") || lower.includes("-"))
|
|
1900
|
+
continue;
|
|
1901
|
+
if (lower.length > clean.length || lower.length < 3)
|
|
1902
|
+
continue;
|
|
1903
|
+
const wordFreq = getFrequency(lower);
|
|
1904
|
+
let isValid = true;
|
|
1905
|
+
for (const [ch, count] of Object.entries(wordFreq)) {
|
|
1906
|
+
if (!targetFreq[ch] || targetFreq[ch] < count) {
|
|
1907
|
+
isValid = false;
|
|
1908
|
+
break;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
if (isValid && lower !== clean) {
|
|
1912
|
+
if (lower.length === clean.length) {
|
|
1913
|
+
exactMatches.push(headword);
|
|
1914
|
+
} else {
|
|
1915
|
+
subMatches.push(headword);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
if (exactMatches.length > 0 || forceExact) {
|
|
1920
|
+
return exactMatches.slice(0, max);
|
|
1921
|
+
}
|
|
1922
|
+
subMatches.sort((a, b) => b.length - a.length || a.localeCompare(b, "tr-TR"));
|
|
1923
|
+
return subMatches.slice(0, max);
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Finds words in TDK that rhyme with the given word (sharing the same ending suffix/letters).
|
|
1927
|
+
* @param word The target word
|
|
1928
|
+
* @param options.minLetters Minimum number of ending characters that must match (default: 3)
|
|
1929
|
+
* @param options.maxResults Maximum number of rhyme results to return (default: 50)
|
|
1930
|
+
*/
|
|
1931
|
+
static async findRhymes(word2, options) {
|
|
1932
|
+
if (!word2 || word2.trim() === "")
|
|
1933
|
+
return [];
|
|
1934
|
+
await this.ensureAutocompleteLoaded();
|
|
1935
|
+
const clean = word2.trim().toLocaleLowerCase("tr-TR");
|
|
1936
|
+
const minLetters = Math.min(options?.minLetters ?? 3, clean.length);
|
|
1937
|
+
const max = options?.maxResults ?? 50;
|
|
1938
|
+
const suffix = clean.slice(-minLetters);
|
|
1939
|
+
const results = [];
|
|
1940
|
+
for (const headword of this.autocompleteCache) {
|
|
1941
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
1942
|
+
if (lower !== clean && lower.endsWith(suffix) && !lower.includes(" ")) {
|
|
1943
|
+
results.push(headword);
|
|
1944
|
+
if (results.length >= max)
|
|
1945
|
+
break;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
return results;
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Performs comprehensive spelling, grammar, and syntax proofreading on a Turkish text.
|
|
1952
|
+
* Detects:
|
|
1953
|
+
* 1. Conjunction 'da/de' erroneously joined to verbs or words (e.g. "gitsende" -> "gitsen de")
|
|
1954
|
+
* 2. Conjunction 'ki' erroneously joined to verbs (e.g. "gördümki" -> "gördüm ki"), respecting SOMBAHÇEMİ exceptions
|
|
1955
|
+
* 3. Question particle 'mi/mı/mu/mü' erroneously joined to words (e.g. "geldimi" -> "geldi mi")
|
|
1956
|
+
* 4. Misspelled words with dictionary suggestions (via edit-distance & morphology)
|
|
1957
|
+
*/
|
|
1958
|
+
static async proofread(text) {
|
|
1959
|
+
if (!text || text.trim() === "") {
|
|
1960
|
+
return { text: text || "", issues: [], isCorrect: true };
|
|
1961
|
+
}
|
|
1962
|
+
await this.ensureAutocompleteLoaded();
|
|
1963
|
+
const issues = [];
|
|
1964
|
+
const SOMBAHCEMI = /* @__PURE__ */ new Set([
|
|
1965
|
+
"sanki",
|
|
1966
|
+
"oysaki",
|
|
1967
|
+
"mademki",
|
|
1968
|
+
"belki",
|
|
1969
|
+
"halbuki",
|
|
1970
|
+
"\xE7\xFCnk\xFC",
|
|
1971
|
+
"me\u011Ferki",
|
|
1972
|
+
"illaki"
|
|
1973
|
+
]);
|
|
1974
|
+
const tokenRegex = /[\p{L}0-9'’]+/gu;
|
|
1975
|
+
let match;
|
|
1976
|
+
while ((match = tokenRegex.exec(text)) !== null) {
|
|
1977
|
+
const rawWord = match[0];
|
|
1978
|
+
const startIndex = match.index;
|
|
1979
|
+
const endIndex = startIndex + rawWord.length;
|
|
1980
|
+
const lower = rawWord.toLocaleLowerCase("tr-TR");
|
|
1981
|
+
if (/^\d+$/.test(lower))
|
|
1982
|
+
continue;
|
|
1983
|
+
let flagged = false;
|
|
1984
|
+
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)?)$/);
|
|
1985
|
+
if (questionMatch) {
|
|
1986
|
+
const base = questionMatch[1];
|
|
1987
|
+
const particle = questionMatch[2];
|
|
1988
|
+
if (base.length >= 2 && (await this.isHeadword(base) || await this.findRoot(base) !== null)) {
|
|
1989
|
+
if (!await this.isHeadword(lower)) {
|
|
1990
|
+
issues.push({
|
|
1991
|
+
type: "question_particle",
|
|
1992
|
+
word: rawWord,
|
|
1993
|
+
startIndex,
|
|
1994
|
+
endIndex,
|
|
1995
|
+
suggestion: `${base} ${particle}`,
|
|
1996
|
+
message: `'${particle}' soru eki kendinden \xF6nceki kelimeden ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
|
|
1997
|
+
});
|
|
1998
|
+
flagged = true;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
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;
|
|
2003
|
+
if (!flagged && lower.endsWith("ki") && lower.length > 3) {
|
|
2004
|
+
const base = lower.slice(0, -2);
|
|
2005
|
+
if (!SOMBAHCEMI.has(lower)) {
|
|
2006
|
+
if (!await this.isHeadword(lower)) {
|
|
2007
|
+
const root = await this.findRoot(base);
|
|
2008
|
+
const isVerb = root && (root.endsWith("mek") || root.endsWith("mak")) || base === "demek" || base === "kald\u0131" || base === "yeter" || base === "bilmem" || VERB_CONJUGATION_REGEX.test(base);
|
|
2009
|
+
if (isVerb) {
|
|
2010
|
+
issues.push({
|
|
2011
|
+
type: "conjunction_ki",
|
|
2012
|
+
word: rawWord,
|
|
2013
|
+
startIndex,
|
|
2014
|
+
endIndex,
|
|
2015
|
+
suggestion: `${base} ki`,
|
|
2016
|
+
message: `'ki' ba\u011Flac\u0131 ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
|
|
2017
|
+
});
|
|
2018
|
+
flagged = true;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
if (!flagged && (lower.endsWith("de") || lower.endsWith("da") || lower.endsWith("te") || lower.endsWith("ta")) && lower.length > 3) {
|
|
2024
|
+
const base = lower.slice(0, -2);
|
|
2025
|
+
const ending = lower.slice(-2);
|
|
2026
|
+
if (!await this.isHeadword(lower)) {
|
|
2027
|
+
const root = await this.findRoot(base);
|
|
2028
|
+
const isVerb = root && (root.endsWith("mek") || root.endsWith("mak")) || VERB_CONJUGATION_REGEX.test(base);
|
|
2029
|
+
if (isVerb) {
|
|
2030
|
+
const correctEnding = ending.startsWith("t") ? ending === "te" ? "de" : "da" : ending;
|
|
2031
|
+
issues.push({
|
|
2032
|
+
type: "conjunction_da",
|
|
2033
|
+
word: rawWord,
|
|
2034
|
+
startIndex,
|
|
2035
|
+
endIndex,
|
|
2036
|
+
suggestion: `${base} ${correctEnding}`,
|
|
2037
|
+
message: `'da/de' ba\u011Flac\u0131 fiillerden sonra her zaman ayr\u0131 yaz\u0131l\u0131r (ba\u011Fla\xE7 olan da/de sertle\u015Fmez).`
|
|
2038
|
+
});
|
|
2039
|
+
flagged = true;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
if (!flagged) {
|
|
2044
|
+
const check = await this.checkSpelling(rawWord);
|
|
2045
|
+
if (!check.isCorrect) {
|
|
2046
|
+
issues.push({
|
|
2047
|
+
type: "spelling",
|
|
2048
|
+
word: rawWord,
|
|
2049
|
+
startIndex,
|
|
2050
|
+
endIndex,
|
|
2051
|
+
suggestion: check.suggestion,
|
|
2052
|
+
message: check.suggestion ? `'${rawWord}' yanl\u0131\u015F yaz\u0131lm\u0131\u015F olabilir. \xD6neri: '${check.suggestion}'` : `'${rawWord}' s\xF6zl\xFCkte bulunamad\u0131.`
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
return {
|
|
2058
|
+
text,
|
|
2059
|
+
issues,
|
|
2060
|
+
isCorrect: issues.length === 0
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
1091
2063
|
};
|
|
1092
2064
|
|
|
1093
2065
|
// src/cli.ts
|
|
1094
2066
|
var rawArgs = process.argv.slice(2);
|
|
1095
2067
|
var jsonMode = rawArgs.includes("--json");
|
|
1096
2068
|
var args = rawArgs.filter((a) => a !== "--json");
|
|
2069
|
+
var isColor = !jsonMode && Boolean(process.stdout.isTTY);
|
|
2070
|
+
var c = {
|
|
2071
|
+
bold: (s) => isColor ? `\x1B[1m${s}\x1B[0m` : s,
|
|
2072
|
+
dim: (s) => isColor ? `\x1B[2m${s}\x1B[0m` : s,
|
|
2073
|
+
green: (s) => isColor ? `\x1B[32m${s}\x1B[0m` : s,
|
|
2074
|
+
yellow: (s) => isColor ? `\x1B[33m${s}\x1B[0m` : s,
|
|
2075
|
+
cyan: (s) => isColor ? `\x1B[36m${s}\x1B[0m` : s,
|
|
2076
|
+
red: (s) => isColor ? `\x1B[31m${s}\x1B[0m` : s
|
|
2077
|
+
};
|
|
1097
2078
|
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
1098
2079
|
"ara",
|
|
1099
2080
|
"anlam",
|
|
@@ -1101,7 +2082,11 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
1101
2082
|
"ornek",
|
|
1102
2083
|
"hece",
|
|
1103
2084
|
"uyum",
|
|
2085
|
+
"kucukuyum",
|
|
1104
2086
|
"yazim",
|
|
2087
|
+
"kok",
|
|
2088
|
+
"stem",
|
|
2089
|
+
"deyim",
|
|
1105
2090
|
"gunun",
|
|
1106
2091
|
"rastgele",
|
|
1107
2092
|
"esanlam",
|
|
@@ -1112,6 +2097,14 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
1112
2097
|
"karsilastir",
|
|
1113
2098
|
"analiz",
|
|
1114
2099
|
"oneri",
|
|
2100
|
+
"bulmaca",
|
|
2101
|
+
"pattern",
|
|
2102
|
+
"anagram",
|
|
2103
|
+
"kafiye",
|
|
2104
|
+
"rhyme",
|
|
2105
|
+
"denetle",
|
|
2106
|
+
"proofread",
|
|
2107
|
+
"repl",
|
|
1115
2108
|
"kubbealti",
|
|
1116
2109
|
"nisanyan",
|
|
1117
2110
|
"viki"
|
|
@@ -1133,17 +2126,93 @@ function printError(message) {
|
|
|
1133
2126
|
if (jsonMode) {
|
|
1134
2127
|
console.log(JSON.stringify({ error: message }));
|
|
1135
2128
|
} else {
|
|
1136
|
-
console.log(`Hata: ${message}`);
|
|
2129
|
+
console.log(c.red(`Hata: ${message}`));
|
|
1137
2130
|
}
|
|
1138
2131
|
}
|
|
2132
|
+
async function startRepl() {
|
|
2133
|
+
const readline = await import("readline");
|
|
2134
|
+
const rl = readline.createInterface({
|
|
2135
|
+
input: process.stdin,
|
|
2136
|
+
output: process.stdout,
|
|
2137
|
+
prompt: c.cyan("tdk> ")
|
|
2138
|
+
});
|
|
2139
|
+
console.log(c.bold("TDK \u0130nteraktif S\xF6zl\xFCk Kabu\u011Fu (\xC7\u0131kmak i\xE7in 'exit' veya Ctrl+C)"));
|
|
2140
|
+
console.log(c.dim("Komutlar: ara <kelime>, hece <kelime>, bulmaca <desen>, denetle <metin> veya do\u011Frudan kelime"));
|
|
2141
|
+
rl.prompt();
|
|
2142
|
+
rl.on("line", async (line) => {
|
|
2143
|
+
const trimmed = line.trim();
|
|
2144
|
+
if (!trimmed) {
|
|
2145
|
+
rl.prompt();
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
if (trimmed === "exit" || trimmed === "quit" || trimmed === ".exit") {
|
|
2149
|
+
rl.close();
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
const parts = trimmed.split(/\s+/);
|
|
2153
|
+
let subCmd = parts[0].toLowerCase();
|
|
2154
|
+
let subArg = parts.slice(1).join(" ");
|
|
2155
|
+
if (!KNOWN_COMMANDS.has(subCmd)) {
|
|
2156
|
+
subArg = trimmed;
|
|
2157
|
+
subCmd = "anlam";
|
|
2158
|
+
}
|
|
2159
|
+
try {
|
|
2160
|
+
if (subCmd === "ara" || subCmd === "anlam") {
|
|
2161
|
+
const meanings = await TDK.getMeanings(subArg);
|
|
2162
|
+
if (meanings.length === 0)
|
|
2163
|
+
console.log(c.dim("Sonu\xE7 bulunamad\u0131."));
|
|
2164
|
+
else
|
|
2165
|
+
meanings.forEach((m, i) => console.log(`${i + 1}. ${c.green(m)}`));
|
|
2166
|
+
} else if (subCmd === "koken") {
|
|
2167
|
+
const origin = await TDK.getOrigin(subArg);
|
|
2168
|
+
console.log(`K\xF6ken: ${c.cyan(origin || "Bilinmiyor")}`);
|
|
2169
|
+
} else if (subCmd === "hece") {
|
|
2170
|
+
const s = TDK.syllabicate(subArg);
|
|
2171
|
+
console.log(`Heceler: ${c.yellow(s.join("-"))}`);
|
|
2172
|
+
} else if (subCmd === "uyum") {
|
|
2173
|
+
const h = TDK.checkVowelHarmony(subArg);
|
|
2174
|
+
console.log(`B\xFCy\xFCk \xDCnl\xFC Uyumu: ${h ? c.green("Uyar") : c.red("Uymaz")}`);
|
|
2175
|
+
} else if (subCmd === "kucukuyum") {
|
|
2176
|
+
const h = TDK.checkLabialHarmony(subArg);
|
|
2177
|
+
console.log(`K\xFC\xE7\xFCk \xDCnl\xFC Uyumu: ${h ? c.green("Uyar") : c.red("Uymaz")}`);
|
|
2178
|
+
} else if (subCmd === "bulmaca" || subCmd === "pattern") {
|
|
2179
|
+
const matches = await TDK.patternSearch(subArg);
|
|
2180
|
+
console.log(matches.slice(0, 15).join(", "));
|
|
2181
|
+
} else if (subCmd === "denetle" || subCmd === "proofread") {
|
|
2182
|
+
const res = await TDK.proofread(subArg);
|
|
2183
|
+
if (res.isCorrect)
|
|
2184
|
+
console.log(c.green("\u2713 Sorun bulunamad\u0131."));
|
|
2185
|
+
else
|
|
2186
|
+
res.issues.forEach((iss) => console.log(`- ${c.yellow(iss.word)}: ${iss.message}${iss.suggestion ? " -> " + c.green(iss.suggestion) : ""}`));
|
|
2187
|
+
} else {
|
|
2188
|
+
console.log(c.dim("\xD6rnek komutlar: 'ara kalem', 'hece elektrik', 'bulmaca k_l_m', 'denetle Bug\xFCn evdeyim'"));
|
|
2189
|
+
}
|
|
2190
|
+
} catch (e) {
|
|
2191
|
+
console.log(c.red(`Hata: ${e?.message || e}`));
|
|
2192
|
+
}
|
|
2193
|
+
rl.prompt();
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
1139
2196
|
async function run() {
|
|
1140
|
-
if (!command
|
|
2197
|
+
if (!command) {
|
|
2198
|
+
if (process.stdin.isTTY) {
|
|
2199
|
+
await startRepl();
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
1141
2202
|
console.log("Kullan\u0131m: tdk [komut] <kelime> [--json]");
|
|
1142
2203
|
console.log(
|
|
1143
|
-
"Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, kubbealti, nisanyan, viki"
|
|
2204
|
+
"Komutlar: ara, anlam, koken, ornek, hece, uyum, kucukuyum, yazim, kok, deyim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, bulmaca, anagram, kafiye, denetle, repl, kubbealti, nisanyan, viki"
|
|
1144
2205
|
);
|
|
1145
2206
|
console.log("Not: Komut belirtilmezse do\u011Frudan kelime anlam\u0131 aran\u0131r (\xF6rn: tdk selam)");
|
|
1146
|
-
process.exit(
|
|
2207
|
+
process.exit(1);
|
|
2208
|
+
}
|
|
2209
|
+
if (command === "--help" || command === "-h") {
|
|
2210
|
+
console.log("Kullan\u0131m: tdk [komut] <kelime> [--json]");
|
|
2211
|
+
console.log(
|
|
2212
|
+
"Komutlar: ara, anlam, koken, ornek, hece, uyum, kucukuyum, yazim, kok, deyim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, bulmaca, anagram, kafiye, denetle, repl, kubbealti, nisanyan, viki"
|
|
2213
|
+
);
|
|
2214
|
+
console.log("Not: Komut belirtilmezse do\u011Frudan kelime anlam\u0131 aran\u0131r (\xF6rn: tdk selam)");
|
|
2215
|
+
process.exit(0);
|
|
1147
2216
|
}
|
|
1148
2217
|
TDK.enableCache(false);
|
|
1149
2218
|
try {
|
|
@@ -1202,19 +2271,63 @@ async function run() {
|
|
|
1202
2271
|
);
|
|
1203
2272
|
break;
|
|
1204
2273
|
}
|
|
2274
|
+
case "kucukuyum":
|
|
2275
|
+
case "labial": {
|
|
2276
|
+
if (!word)
|
|
2277
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
2278
|
+
const isHarmony = TDK.checkLabialHarmony(word);
|
|
2279
|
+
printResult(
|
|
2280
|
+
{ word, labialHarmony: isHarmony },
|
|
2281
|
+
() => console.log(`K\xFC\xE7\xFCk \xDCnl\xFC Uyumu: ${isHarmony ? "Uyar" : "Uymaz"}`)
|
|
2282
|
+
);
|
|
2283
|
+
break;
|
|
2284
|
+
}
|
|
1205
2285
|
case "yazim": {
|
|
1206
2286
|
if (!word)
|
|
1207
2287
|
throw new Error("Kelime belirtmelisiniz.");
|
|
1208
2288
|
const spellResult = await TDK.checkSpelling(word);
|
|
1209
2289
|
printResult(spellResult, () => {
|
|
1210
2290
|
if (spellResult.isCorrect) {
|
|
1211
|
-
|
|
2291
|
+
if (spellResult.isInflected && spellResult.root) {
|
|
2292
|
+
console.log(`Do\u011Fru yaz\u0131m (\xE7ekimli bi\xE7im, k\xF6k: ${spellResult.root}).`);
|
|
2293
|
+
} else {
|
|
2294
|
+
console.log("Do\u011Fru yaz\u0131m.");
|
|
2295
|
+
}
|
|
1212
2296
|
} else {
|
|
1213
2297
|
console.log(`Yanl\u0131\u015F yaz\u0131m.${spellResult.suggestion ? " Do\u011Frusu: " + spellResult.suggestion : ""}`);
|
|
1214
2298
|
}
|
|
1215
2299
|
});
|
|
1216
2300
|
break;
|
|
1217
2301
|
}
|
|
2302
|
+
case "kok":
|
|
2303
|
+
case "stem": {
|
|
2304
|
+
if (!word)
|
|
2305
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
2306
|
+
const stemResult = await TDK.stem(word);
|
|
2307
|
+
printResult(stemResult, () => {
|
|
2308
|
+
if (!stemResult) {
|
|
2309
|
+
console.log("K\xF6k bulunamad\u0131.");
|
|
2310
|
+
} else if (stemResult.isInflected) {
|
|
2311
|
+
console.log(`K\xF6k: ${stemResult.root} (\xE7ekimli bi\xE7im)`);
|
|
2312
|
+
} else {
|
|
2313
|
+
console.log(`K\xF6k: ${stemResult.root} (yal\u0131n bi\xE7im)`);
|
|
2314
|
+
}
|
|
2315
|
+
});
|
|
2316
|
+
break;
|
|
2317
|
+
}
|
|
2318
|
+
case "deyim": {
|
|
2319
|
+
if (!word)
|
|
2320
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
2321
|
+
const proverbs = await TDK.getProverbs(word);
|
|
2322
|
+
printResult(proverbs, () => {
|
|
2323
|
+
if (proverbs.length === 0) {
|
|
2324
|
+
console.log("Atas\xF6z\xFC/deyim bulunamad\u0131.");
|
|
2325
|
+
} else {
|
|
2326
|
+
proverbs.forEach((p, i) => console.log(`${i + 1}. ${p}`));
|
|
2327
|
+
}
|
|
2328
|
+
});
|
|
2329
|
+
break;
|
|
2330
|
+
}
|
|
1218
2331
|
case "gunun": {
|
|
1219
2332
|
const wotd = await TDK.getWordOfTheDay();
|
|
1220
2333
|
printResult(wotd, () => {
|
|
@@ -1321,7 +2434,8 @@ async function run() {
|
|
|
1321
2434
|
} else {
|
|
1322
2435
|
analysis.forEach((a) => {
|
|
1323
2436
|
if (a.found) {
|
|
1324
|
-
|
|
2437
|
+
const rootLabel = a.isInflected && a.root ? ` (k\xF6k: ${a.root})` : "";
|
|
2438
|
+
console.log(`${a.word}${rootLabel}: ${a.meaning ?? "-"} (${a.origin})`);
|
|
1325
2439
|
} else {
|
|
1326
2440
|
console.log(`${a.word}: bulunamad\u0131`);
|
|
1327
2441
|
}
|
|
@@ -1343,6 +2457,76 @@ async function run() {
|
|
|
1343
2457
|
});
|
|
1344
2458
|
break;
|
|
1345
2459
|
}
|
|
2460
|
+
case "bulmaca":
|
|
2461
|
+
case "pattern": {
|
|
2462
|
+
if (!word)
|
|
2463
|
+
throw new Error("Desen belirtmelisiniz (\xF6rn: k_l_m).");
|
|
2464
|
+
const matches = await TDK.patternSearch(word);
|
|
2465
|
+
printResult(matches, () => {
|
|
2466
|
+
if (matches.length === 0) {
|
|
2467
|
+
console.log("E\u015Fle\u015Fen kelime bulunamad\u0131.");
|
|
2468
|
+
} else {
|
|
2469
|
+
console.log(c.bold(`Bulunan Kelimeler (${matches.length}):`));
|
|
2470
|
+
matches.forEach((m, i) => console.log(`${i + 1}. ${c.cyan(m)}`));
|
|
2471
|
+
}
|
|
2472
|
+
});
|
|
2473
|
+
break;
|
|
2474
|
+
}
|
|
2475
|
+
case "anagram": {
|
|
2476
|
+
if (!word)
|
|
2477
|
+
throw new Error("Harfler belirtmelisiniz.");
|
|
2478
|
+
const anagrams = await TDK.findAnagrams(word);
|
|
2479
|
+
printResult(anagrams, () => {
|
|
2480
|
+
if (anagrams.length === 0) {
|
|
2481
|
+
console.log("Anagram veya bu harflerle t\xFCretilebilecek kelime bulunamad\u0131.");
|
|
2482
|
+
} else {
|
|
2483
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR").replace(/[^a-zçğıöşüâîû]/gi, "");
|
|
2484
|
+
const hasExact = anagrams.some((a) => a.length === clean.length);
|
|
2485
|
+
const title = hasExact ? `Anagramlar (${anagrams.length}):` : `Birebir anagram bulunamad\u0131. Bu harflerle t\xFCretilen kelimeler (${anagrams.length}):`;
|
|
2486
|
+
console.log(c.bold(title));
|
|
2487
|
+
anagrams.forEach((a, i) => console.log(`${i + 1}. ${c.green(a)} ${c.dim(`(${a.length} harf)`)}`));
|
|
2488
|
+
}
|
|
2489
|
+
});
|
|
2490
|
+
break;
|
|
2491
|
+
}
|
|
2492
|
+
case "kafiye":
|
|
2493
|
+
case "rhyme": {
|
|
2494
|
+
if (!word)
|
|
2495
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
2496
|
+
const rhymes = await TDK.findRhymes(word);
|
|
2497
|
+
printResult(rhymes, () => {
|
|
2498
|
+
if (rhymes.length === 0) {
|
|
2499
|
+
console.log("Kafiye bulunamad\u0131.");
|
|
2500
|
+
} else {
|
|
2501
|
+
console.log(c.bold(`Kafiyeli Kelimeler (${rhymes.length}):`));
|
|
2502
|
+
rhymes.forEach((r, i) => console.log(`${i + 1}. ${c.yellow(r)}`));
|
|
2503
|
+
}
|
|
2504
|
+
});
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
2507
|
+
case "denetle":
|
|
2508
|
+
case "proofread": {
|
|
2509
|
+
if (!word)
|
|
2510
|
+
throw new Error("Metin belirtmelisiniz.");
|
|
2511
|
+
const result = await TDK.proofread(word);
|
|
2512
|
+
printResult(result, () => {
|
|
2513
|
+
if (result.isCorrect) {
|
|
2514
|
+
console.log(c.green("\u2713 Metinde imla veya ba\u011Fla\xE7 hatas\u0131 tespit edilmedi."));
|
|
2515
|
+
} else {
|
|
2516
|
+
console.log(c.bold(c.red(`Metinde ${result.issues.length} olas\u0131 sorun tespit edildi:`)));
|
|
2517
|
+
result.issues.forEach((issue, i) => {
|
|
2518
|
+
const label = c.yellow(`[${issue.type}]`);
|
|
2519
|
+
const sug = issue.suggestion ? c.green(` -> \xD6neri: ${issue.suggestion}`) : "";
|
|
2520
|
+
console.log(`${i + 1}. ${label} "${c.bold(issue.word)}": ${issue.message}${sug}`);
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
});
|
|
2524
|
+
break;
|
|
2525
|
+
}
|
|
2526
|
+
case "repl": {
|
|
2527
|
+
await startRepl();
|
|
2528
|
+
break;
|
|
2529
|
+
}
|
|
1346
2530
|
case "kubbealti": {
|
|
1347
2531
|
if (!word)
|
|
1348
2532
|
throw new Error("Kelime belirtmelisiniz.");
|