tdk-api-wrapper 1.5.0 → 1.6.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/dist/index.js CHANGED
@@ -584,6 +584,13 @@ function getStemCandidates(word, minStemLength = 2, maxDepth = 4) {
584
584
  seen.add(apostropheStem);
585
585
  }
586
586
  }
587
+ const bareInfinitives = restoreInfinitive(normalized);
588
+ for (const inf of bareInfinitives) {
589
+ if (!seen.has(inf) && inf !== normalized) {
590
+ seen.add(inf);
591
+ candidatesWithWeight.push({ candidate: inf, baseLength: normalized.length });
592
+ }
593
+ }
587
594
  let frontier = [normalized];
588
595
  for (let depth = 0; depth < maxDepth; depth++) {
589
596
  const nextFrontier = [];
@@ -633,6 +640,210 @@ var path = __toESM(require("path"));
633
640
  var os = __toESM(require("os"));
634
641
  var https = __toESM(require("https"));
635
642
  var tls = __toESM(require("tls"));
643
+ var COMMON_MISSPELLINGS = {
644
+ // -şey ile biten ve ayrı yazılması zorunlu söz öbekleri
645
+ her\u015Fey: "her \u015Fey",
646
+ hersey: "her \u015Fey",
647
+ bir\u015Fey: "bir \u015Fey",
648
+ birsey: "bir \u015Fey",
649
+ hi\u00E7bir\u015Fey: "hi\xE7bir \u015Fey",
650
+ hicbirsey: "hi\xE7bir \u015Fey",
651
+ \u00E7ok\u015Fey: "\xE7ok \u015Fey",
652
+ coksey: "\xE7ok \u015Fey",
653
+ \u015Feyler: "\u015Feyler",
654
+ seyler: "\u015Feyler",
655
+ herhangibir\u015Fey: "herhangi bir \u015Fey",
656
+ herhangibirsey: "herhangi bir \u015Fey",
657
+ // Sıkça birleşik yazılan ama ayrı yazılması gereken sözler
658
+ herg\u00FCn: "her g\xFCn",
659
+ hergun: "her g\xFCn",
660
+ herzaman: "her zaman",
661
+ heran: "her an",
662
+ heryer: "her yer",
663
+ herbiri: "her biri",
664
+ pek\u00E7ok: "pek \xE7ok",
665
+ pekcok: "pek \xE7ok",
666
+ pekaz: "pek az",
667
+ yada: "ya da",
668
+ tabiki: "tabii ki",
669
+ tabiiki: "tabii ki",
670
+ sa\u011Fol: "sa\u011F ol",
671
+ sagol: "sa\u011F ol",
672
+ sa\u011Folun: "sa\u011F olun",
673
+ sagolun: "sa\u011F olun",
674
+ ho\u015F\u00E7akal: "ho\u015F\xE7a kal",
675
+ hoscakal: "ho\u015F\xE7a kal",
676
+ ho\u015Fgeldin: "ho\u015F geldin",
677
+ hosgeldin: "ho\u015F geldin",
678
+ ho\u015Fgeldiniz: "ho\u015F geldiniz",
679
+ hosgeldiniz: "ho\u015F geldiniz",
680
+ ho\u015Fbulduk: "ho\u015F bulduk",
681
+ hosbulduk: "ho\u015F bulduk",
682
+ yan\u0131s\u0131ra: "yan\u0131 s\u0131ra",
683
+ yanisira: "yan\u0131 s\u0131ra",
684
+ pe\u015Fis\u0131ra: "pe\u015Fi s\u0131ra",
685
+ pesisira: "pe\u015Fi s\u0131ra",
686
+ ard\u0131s\u0131ra: "ard\u0131 s\u0131ra",
687
+ ardisira: "ard\u0131 s\u0131ra",
688
+ artarda: "art arda",
689
+ y\u00FCzy\u00FCze: "y\xFCz y\xFCze",
690
+ yuzyuze: "y\xFCz y\xFCze",
691
+ elele: "el ele",
692
+ g\u00F6zg\u00F6ze: "g\xF6z g\xF6ze",
693
+ ba\u015Fba\u015Fa: "ba\u015F ba\u015Fa",
694
+ basbasa: "ba\u015F ba\u015Fa",
695
+ yanyana: "yan yana",
696
+ i\u00E7i\u00E7e: "i\xE7 i\xE7e",
697
+ icice: "i\xE7 i\xE7e",
698
+ \u00FCst\u00FCste: "\xFCst \xFCste",
699
+ ustuste: "\xFCst \xFCste",
700
+ altalta: "alt alta",
701
+ \u00F6ns\u00F6z: "\xF6n s\xF6z",
702
+ onsoz: "\xF6n s\xF6z",
703
+ \u00F6nyarg\u0131: "\xF6n yarg\u0131",
704
+ onyargi: "\xF6n yarg\u0131",
705
+ farketmek: "fark etmek",
706
+ farketti: "fark etti",
707
+ farkettim: "fark ettim",
708
+ farkeder: "fark eder",
709
+ farketmez: "fark etmez",
710
+ terketmek: "terk etmek",
711
+ terketti: "terk etti",
712
+ ay\u0131rdetmek: "ay\u0131rt etmek",
713
+ ay\u0131rtetmek: "ay\u0131rt etmek",
714
+ arzetmek: "arz etmek",
715
+ arzederim: "arz ederim",
716
+ varolmak: "var olmak",
717
+ yokolmak: "yok olmak",
718
+ haketmek: "hak etmek",
719
+ haketti: "hak etti",
720
+ hakkaten: "hakikaten",
721
+ hi\u00E7kimse: "hi\xE7 kimse",
722
+ hickimse: "hi\xE7 kimse",
723
+ // Ünlü düşmesi yapılmaması gereken yer bildiren sözler (TDK Kural 15)
724
+ burda: "burada",
725
+ burdan: "buradan",
726
+ \u015Furda: "\u015Furada",
727
+ surda: "\u015Furada",
728
+ \u015Furdan: "\u015Furadan",
729
+ surdan: "\u015Furadan",
730
+ orda: "orada",
731
+ ordan: "oradan",
732
+ i\u00E7erde: "i\xE7eride",
733
+ icerde: "i\xE7eride",
734
+ i\u00E7erden: "i\xE7eriden",
735
+ icerden: "i\xE7eriden",
736
+ d\u0131\u015Farda: "d\u0131\u015Far\u0131da",
737
+ disarda: "d\u0131\u015Far\u0131da",
738
+ d\u0131\u015Fardan: "d\u0131\u015Far\u0131dan",
739
+ disardan: "d\u0131\u015Far\u0131dan",
740
+ yukarda: "yukar\u0131da",
741
+ yukardan: "yukar\u0131dan",
742
+ // Sıkça yanlış yazılan sözcükler
743
+ herkez: "herkes",
744
+ yanl\u0131z: "yaln\u0131z",
745
+ yaln\u0131\u015F: "yanl\u0131\u015F",
746
+ orjinal: "orijinal",
747
+ labaratuar: "laboratuvar",
748
+ laboratuar: "laboratuvar",
749
+ \u015F\u00F6f\u00F6r: "\u015Fof\xF6r",
750
+ sofor: "\u015Fof\xF6r",
751
+ egzos: "egzoz",
752
+ eksoz: "egzoz",
753
+ ekzoz: "egzoz",
754
+ kiprik: "kirpik",
755
+ kirbit: "kibrit",
756
+ klavuz: "k\u0131lavuz",
757
+ k\u0131ravat: "kravat",
758
+ s\u00FCpriz: "s\xFCrpriz",
759
+ supriz: "s\xFCrpriz",
760
+ raslant\u0131: "rastlant\u0131",
761
+ hastahane: "hastane",
762
+ pastahane: "pastane",
763
+ postahane: "postane",
764
+ eczahane: "eczane",
765
+ meyva: "meyve",
766
+ sarm\u0131sak: "sar\u0131msak",
767
+ dinazor: "dinozor",
768
+ pantalon: "pantolon",
769
+ tesbih: "tespih",
770
+ ah\u00E7\u0131: "a\u015F\xE7\u0131",
771
+ matba: "matbaa",
772
+ idda: "iddia",
773
+ iddaa: "iddia",
774
+ muhattap: "muhatap",
775
+ tra\u015F: "t\u0131ra\u015F",
776
+ karn\u0131bahar: "karnabahar",
777
+ kareografi: "koreografi",
778
+ poa\u00E7a: "po\u011Fa\xE7a",
779
+ poha\u00E7a: "po\u011Fa\xE7a",
780
+ \u015Farz: "\u015Farj",
781
+ sarj: "\u015Farj",
782
+ makina: "makine",
783
+ m\u00FCsade: "m\xFCsaade",
784
+ entellekt\u00FCel: "entelekt\xFCel",
785
+ inisiyatif: "inisiyatif",
786
+ insiyatif: "inisiyatif",
787
+ sezeryan: "sezaryen",
788
+ dok\u00FCman: "dok\xFCman",
789
+ d\u00F6k\u00FCman: "dok\xFCman",
790
+ erozyon: "erozyon",
791
+ erizyon: "erozyon",
792
+ anane: "anneanne",
793
+ babaanne: "babaanne"
794
+ };
795
+ var SEY_EXCEPTIONS = /* @__PURE__ */ new Set(["d\xFC\u015Fey", "e\u015Fey", "konsey", "jersey", "\u015Fey"]);
796
+ var KEYBOARD_ROWS = [
797
+ ["qwertyu\u0131op\u011F\xFC", 0],
798
+ ["asdfghjkl\u015Fi", 0.5],
799
+ ["zxcvbnm\xF6\xE7", 1]
800
+ ];
801
+ var KEYBOARD_COORDS = (() => {
802
+ const coords = {};
803
+ KEYBOARD_ROWS.forEach(([keys, offset], row) => {
804
+ [...keys].forEach((key, col) => {
805
+ coords[key] = [col + offset, row];
806
+ });
807
+ });
808
+ return coords;
809
+ })();
810
+ var DIACRITIC_SIBLINGS = {
811
+ \u0131: "i",
812
+ i: "\u0131",
813
+ \u00F6: "o",
814
+ o: "\xF6",
815
+ \u00FC: "u",
816
+ u: "\xFC",
817
+ \u015F: "s",
818
+ s: "\u015F",
819
+ \u00E7: "c",
820
+ c: "\xE7",
821
+ \u011F: "g",
822
+ g: "\u011F",
823
+ \u00E2: "a",
824
+ a: "\xE2"
825
+ };
826
+ var KEYBOARD_ROW_SUB_COST = 0.4;
827
+ var KEYBOARD_DIAGONAL_SUB_COST = 0.55;
828
+ var DIACRITIC_SUB_COST = 0.3;
829
+ var TRANSPOSITION_COST = 0.8;
830
+ function keyboardSubCost(a, b) {
831
+ if (a === b)
832
+ return 0;
833
+ if (DIACRITIC_SIBLINGS[a] === b)
834
+ return DIACRITIC_SUB_COST;
835
+ const pa = KEYBOARD_COORDS[a];
836
+ const pb = KEYBOARD_COORDS[b];
837
+ if (!pa || !pb)
838
+ return 1;
839
+ const dx = Math.abs(pa[0] - pb[0]);
840
+ const dy = Math.abs(pa[1] - pb[1]);
841
+ if (dy === 0 && dx <= 1 + 1e-9)
842
+ return KEYBOARD_ROW_SUB_COST;
843
+ if (dy === 1 && dx <= 1 + 1e-9)
844
+ return KEYBOARD_DIAGONAL_SUB_COST;
845
+ return 1;
846
+ }
636
847
  var TDK = class {
637
848
  static BASE_URL = "https://sozluk.gov.tr";
638
849
  static AUDIO_API_HOST = "api.sozluk.gov.tr";
@@ -1196,25 +1407,45 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1196
1407
  * Checks spelling and returns suggestions if wrong.
1197
1408
  */
1198
1409
  static async checkSpelling(word) {
1410
+ if (!word || word.trim() === "") {
1411
+ return { isCorrect: false, word };
1412
+ }
1413
+ const cleanWord = word.trim().toLocaleLowerCase("tr-TR");
1199
1414
  const results = await this.getWord(word);
1200
1415
  if (results.length > 0) {
1201
1416
  return { isCorrect: true, word };
1202
1417
  }
1418
+ if (COMMON_MISSPELLINGS[cleanWord]) {
1419
+ return { isCorrect: false, word, suggestion: COMMON_MISSPELLINGS[cleanWord] };
1420
+ }
1421
+ const seyMatch = cleanWord.match(/^(.+?)(?:şey|sey)([ıiuaeüodekmnl]+)?$/);
1422
+ if (seyMatch && !SEY_EXCEPTIONS.has(cleanWord)) {
1423
+ let prefix = seyMatch[1];
1424
+ const suffix = seyMatch[2] || "";
1425
+ if (prefix === "hicbir")
1426
+ prefix = "hi\xE7bir";
1427
+ if (prefix === "cok")
1428
+ prefix = "\xE7ok";
1429
+ return {
1430
+ isCorrect: false,
1431
+ word,
1432
+ suggestion: `${prefix} \u015Fey${suffix}`
1433
+ };
1434
+ }
1203
1435
  const daily = await this.getDailyContent();
1204
1436
  if (daily) {
1205
- const syydMatch = daily.syyd.find((s) => s.yanliskelime.toLocaleLowerCase("tr-TR") === word.toLocaleLowerCase("tr-TR"));
1437
+ const syydMatch = daily.syyd.find((s) => s.yanliskelime.toLocaleLowerCase("tr-TR") === cleanWord);
1206
1438
  if (syydMatch) {
1207
1439
  return { isCorrect: false, word, suggestion: syydMatch.dogrukelime };
1208
1440
  }
1209
- const mixMatch = daily.karistirma.find((s) => s.yanlis.toLocaleLowerCase("tr-TR") === word.toLocaleLowerCase("tr-TR"));
1441
+ const mixMatch = daily.karistirma.find((s) => s.yanlis.toLocaleLowerCase("tr-TR") === cleanWord);
1210
1442
  if (mixMatch) {
1211
1443
  return { isCorrect: false, word, suggestion: mixMatch.dogru };
1212
1444
  }
1213
1445
  }
1214
1446
  const root = await this.findRoot(word);
1215
1447
  if (root) {
1216
- const cleanWord2 = word.trim().toLocaleLowerCase("tr-TR");
1217
- const isInflected = root !== cleanWord2;
1448
+ const isInflected = root !== cleanWord;
1218
1449
  return {
1219
1450
  isCorrect: true,
1220
1451
  word,
@@ -1225,24 +1456,32 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1225
1456
  if (this.autocompleteCache.length === 0) {
1226
1457
  this.autocompleteCache = await this.fetchAutocompleteData();
1227
1458
  }
1228
- const cleanWord = word.trim().toLocaleLowerCase("tr-TR");
1459
+ for (const candidate of this.autocompleteCache) {
1460
+ if (candidate.includes(" ")) {
1461
+ const candidateNoSpace = candidate.replace(/\s+/g, "").toLocaleLowerCase("tr-TR");
1462
+ if (candidateNoSpace === cleanWord) {
1463
+ return { isCorrect: false, word, suggestion: candidate };
1464
+ }
1465
+ }
1466
+ }
1229
1467
  let best = null;
1230
1468
  for (const candidate of this.autocompleteCache) {
1231
1469
  if (candidate.includes(" ") || candidate !== candidate.toLocaleLowerCase("tr-TR"))
1232
1470
  continue;
1233
1471
  if (Math.abs(candidate.length - cleanWord.length) > 2)
1234
1472
  continue;
1235
- const distance = this.damerauLevenshtein(cleanWord, candidate);
1236
- if (distance === 0)
1473
+ const rawDist = this.damerauLevenshtein(cleanWord, candidate);
1474
+ if (rawDist === 0 || rawDist > 2)
1237
1475
  continue;
1238
1476
  const firstMismatch = candidate[0] === cleanWord[0] ? 0 : 1;
1239
1477
  const lengthMismatch = candidate.length === cleanWord.length ? 0 : 1;
1240
- const better = !best || distance < best.distance || distance === best.distance && firstMismatch < best.firstMismatch || distance === best.distance && firstMismatch === best.firstMismatch && lengthMismatch < best.lengthMismatch;
1478
+ const score = this.keyboardAwareDistance(cleanWord, candidate) + (firstMismatch > 0 ? 1.2 : 0);
1479
+ const better = !best || score < best.score - 1e-9 || Math.abs(score - best.score) < 1e-9 && firstMismatch < best.firstMismatch || Math.abs(score - best.score) < 1e-9 && firstMismatch === best.firstMismatch && lengthMismatch < best.lengthMismatch;
1241
1480
  if (better) {
1242
- best = { candidate, distance, firstMismatch, lengthMismatch };
1481
+ best = { candidate, score, rawDist, firstMismatch, lengthMismatch };
1243
1482
  }
1244
1483
  }
1245
- if (best && best.distance <= 2) {
1484
+ if (best && best.rawDist <= 2 && (best.firstMismatch === 0 || best.rawDist <= 1)) {
1246
1485
  return { isCorrect: false, word, suggestion: best.candidate };
1247
1486
  }
1248
1487
  return { isCorrect: false, word };
@@ -1771,6 +2010,32 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1771
2010
  }
1772
2011
  return dp[a.length][b.length];
1773
2012
  }
2013
+ /**
2014
+ * Keyboard- and diacritic-aware edit distance: same optimal-string-alignment
2015
+ * recurrence as {@link damerauLevenshtein}, but a substitution is charged by
2016
+ * {@link keyboardSubCost} (a fraction of an edit when the two letters are
2017
+ * adjacent on a Turkish Q keyboard or are ASCII/diacritic siblings) and a
2018
+ * transposition costs {@link TRANSPOSITION_COST}. Insertions and deletions
2019
+ * still cost a full 1. Used only to *rank* spelling candidates; the plain
2020
+ * integer distance still gates whether a suggestion is offered at all.
2021
+ */
2022
+ static keyboardAwareDistance(a, b) {
2023
+ const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
2024
+ for (let i = 0; i <= a.length; i++)
2025
+ dp[i][0] = i;
2026
+ for (let j = 0; j <= b.length; j++)
2027
+ dp[0][j] = j;
2028
+ for (let i = 1; i <= a.length; i++) {
2029
+ for (let j = 1; j <= b.length; j++) {
2030
+ const cost = keyboardSubCost(a[i - 1], b[j - 1]);
2031
+ dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
2032
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
2033
+ dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + TRANSPOSITION_COST);
2034
+ }
2035
+ }
2036
+ }
2037
+ return dp[a.length][b.length];
2038
+ }
1774
2039
  /**
1775
2040
  * Fetches multiple words concurrently with a small delay to avoid rate limiting.
1776
2041
  */
@@ -1995,6 +2260,73 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1995
2260
  "me\u011Ferki",
1996
2261
  "illaki"
1997
2262
  ]);
2263
+ const PHRASE_MISTAKES = [
2264
+ {
2265
+ regex: /\bhiç\s+bir\b/gi,
2266
+ suggestion: "hi\xE7bir",
2267
+ message: "'hi\xE7bir' belgisiz s\u0131fat\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
2268
+ type: "spelling"
2269
+ },
2270
+ {
2271
+ regex: /\bbir\s+çok\b/gi,
2272
+ suggestion: "bir\xE7ok",
2273
+ message: "'bir\xE7ok' belgisiz s\u0131fat\u0131/zamiri biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
2274
+ type: "spelling"
2275
+ },
2276
+ {
2277
+ regex: /\bbir\s+kaç\b/gi,
2278
+ suggestion: "birka\xE7",
2279
+ message: "'birka\xE7' belgisiz s\u0131fat\u0131/zamiri biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
2280
+ type: "spelling"
2281
+ },
2282
+ {
2283
+ regex: /\bbir\s+az\b/gi,
2284
+ suggestion: "biraz",
2285
+ message: "'biraz' s\xF6zc\xFC\u011F\xFC biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
2286
+ type: "spelling"
2287
+ },
2288
+ {
2289
+ regex: /\bher\s+hangi\b/gi,
2290
+ suggestion: "herhangi",
2291
+ message: "'herhangi' s\xF6zc\xFC\u011F\xFC biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
2292
+ type: "spelling"
2293
+ },
2294
+ {
2295
+ regex: /\bgit\s+gide\b/gi,
2296
+ suggestion: "gitgide",
2297
+ message: "'gitgide' zarf\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
2298
+ type: "spelling"
2299
+ },
2300
+ {
2301
+ regex: /\bbirden\s+bire\b/gi,
2302
+ suggestion: "birdenbire",
2303
+ message: "'birdenbire' zarf\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
2304
+ type: "spelling"
2305
+ },
2306
+ {
2307
+ regex: /\brast\s+gele\b/gi,
2308
+ suggestion: "rastgele",
2309
+ message: "'rastgele' zarf\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
2310
+ type: "spelling"
2311
+ }
2312
+ ];
2313
+ const coveredRanges = [];
2314
+ for (const pm of PHRASE_MISTAKES) {
2315
+ let pmMatch;
2316
+ while ((pmMatch = pm.regex.exec(text)) !== null) {
2317
+ const start = pmMatch.index;
2318
+ const end = start + pmMatch[0].length;
2319
+ coveredRanges.push({ start, end });
2320
+ issues.push({
2321
+ type: pm.type,
2322
+ word: pmMatch[0],
2323
+ startIndex: start,
2324
+ endIndex: end,
2325
+ suggestion: pm.suggestion,
2326
+ message: pm.message
2327
+ });
2328
+ }
2329
+ }
1998
2330
  const tokenRegex = /[\p{L}0-9'’]+/gu;
1999
2331
  let match;
2000
2332
  while ((match = tokenRegex.exec(text)) !== null) {
@@ -2004,6 +2336,8 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
2004
2336
  const lower = rawWord.toLocaleLowerCase("tr-TR");
2005
2337
  if (/^\d+$/.test(lower))
2006
2338
  continue;
2339
+ if (coveredRanges.some((r) => startIndex >= r.start && endIndex <= r.end))
2340
+ continue;
2007
2341
  let flagged = false;
2008
2342
  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
2343
  if (questionMatch) {
@@ -2029,7 +2363,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
2029
2363
  if (!SOMBAHCEMI.has(lower)) {
2030
2364
  if (!await this.isHeadword(lower)) {
2031
2365
  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);
2366
+ const isVerb = (base === "demek" || base === "kald\u0131" || base === "yeter" || base === "bilmem" || VERB_CONJUGATION_REGEX.test(base)) && (root ? root.endsWith("mek") || root.endsWith("mak") : true);
2033
2367
  if (isVerb) {
2034
2368
  issues.push({
2035
2369
  type: "conjunction_ki",
@@ -2049,7 +2383,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
2049
2383
  const ending = lower.slice(-2);
2050
2384
  if (!await this.isHeadword(lower)) {
2051
2385
  const root = await this.findRoot(base);
2052
- const isVerb = root && (root.endsWith("mek") || root.endsWith("mak")) || VERB_CONJUGATION_REGEX.test(base);
2386
+ const isVerb = VERB_CONJUGATION_REGEX.test(base) && (root ? root.endsWith("mek") || root.endsWith("mak") : false);
2053
2387
  if (isVerb) {
2054
2388
  const correctEnding = ending.startsWith("t") ? ending === "te" ? "de" : "da" : ending;
2055
2389
  issues.push({
@@ -2064,6 +2398,47 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
2064
2398
  }
2065
2399
  }
2066
2400
  }
2401
+ const seyMatch = lower.match(/^(.+?)(?:şey|sey)([ıiuaeüodekmnl]+)?$/);
2402
+ if (!flagged && seyMatch && !SEY_EXCEPTIONS.has(lower)) {
2403
+ let prefix = seyMatch[1];
2404
+ const suffix = seyMatch[2] || "";
2405
+ if (prefix === "hicbir")
2406
+ prefix = "hi\xE7bir";
2407
+ if (prefix === "cok")
2408
+ prefix = "\xE7ok";
2409
+ issues.push({
2410
+ type: "spelling",
2411
+ word: rawWord,
2412
+ startIndex,
2413
+ endIndex,
2414
+ suggestion: `${prefix} \u015Fey${suffix}`,
2415
+ message: "'\u015Fey' s\xF6zc\xFC\u011F\xFC kendinden \xF6nceki kelimeden ayr\u0131 yaz\u0131lmal\u0131d\u0131r."
2416
+ });
2417
+ flagged = true;
2418
+ }
2419
+ if (!flagged && lower === "yada") {
2420
+ issues.push({
2421
+ type: "spelling",
2422
+ word: rawWord,
2423
+ startIndex,
2424
+ endIndex,
2425
+ suggestion: "ya da",
2426
+ message: "'ya da' ba\u011Flac\u0131 her zaman ayr\u0131 yaz\u0131l\u0131r."
2427
+ });
2428
+ flagged = true;
2429
+ }
2430
+ if (!flagged && (lower === "burda" || lower === "\u015Furda" || lower === "surda" || lower === "orda" || lower === "i\xE7erde" || lower === "icerde" || lower === "d\u0131\u015Farda" || lower === "disarda" || lower === "yukarda")) {
2431
+ const correct = COMMON_MISSPELLINGS[lower] || lower;
2432
+ issues.push({
2433
+ type: "spelling",
2434
+ word: rawWord,
2435
+ startIndex,
2436
+ endIndex,
2437
+ suggestion: correct,
2438
+ message: `'${rawWord}' s\xF6zc\xFC\u011F\xFCnde \xFCnl\xFC d\xFC\u015Fmesi yap\u0131lmaz.`
2439
+ });
2440
+ flagged = true;
2441
+ }
2067
2442
  if (!flagged) {
2068
2443
  const check = await this.checkSpelling(rawWord);
2069
2444
  if (!check.isCorrect) {
@@ -2073,11 +2448,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
2073
2448
  startIndex,
2074
2449
  endIndex,
2075
2450
  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.`
2451
+ message: check.suggestion ? `'${rawWord}' yanl\u0131\u015F yaz\u0131lm\u0131\u015F olabilir.` : `'${rawWord}' s\xF6zl\xFCkte bulunamad\u0131.`
2077
2452
  });
2078
2453
  }
2079
2454
  }
2080
2455
  }
2456
+ issues.sort((a, b) => a.startIndex - b.startIndex);
2081
2457
  return {
2082
2458
  text,
2083
2459
  issues,
package/dist/index.mjs CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  restoreInfinitive,
14
14
  restoreVowelDrop,
15
15
  restoreVowelNarrowing
16
- } from "./chunk-7KJHYRJZ.mjs";
16
+ } from "./chunk-ZFWZXNGY.mjs";
17
17
  export {
18
18
  TDK,
19
19
  TDKClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tdk-api-wrapper",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "TDK (Türk Dil Kurumu) unofficial live data API wrapper for Node.js",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/src/cli.ts CHANGED
@@ -53,7 +53,7 @@ const KNOWN_COMMANDS = new Set([
53
53
  let command = args[0];
54
54
  let word = args.slice(1).join(" ");
55
55
 
56
- if (command && !KNOWN_COMMANDS.has(command) && command !== "--help" && command !== "-h") {
56
+ if (command && !KNOWN_COMMANDS.has(command) && command !== "--help" && command !== "-h" && command !== "--version" && command !== "-v") {
57
57
  word = args.join(" ");
58
58
  command = "anlam";
59
59
  }
@@ -153,6 +153,11 @@ async function run() {
153
153
  process.exit(1);
154
154
  }
155
155
 
156
+ if (command === "--version" || command === "-v") {
157
+ console.log("tdk-api-wrapper v1.5.1");
158
+ process.exit(0);
159
+ }
160
+
156
161
  if (command === "--help" || command === "-h") {
157
162
  console.log("Kullanım: tdk [komut] <kelime> [--json]");
158
163
  console.log(
package/src/morphology.ts CHANGED
@@ -230,6 +230,15 @@ export function getStemCandidates(
230
230
  }
231
231
  }
232
232
 
233
+ // Bare verb imperative candidates (e.g. "söyle" -> "söylemek", "oku" -> "okumak")
234
+ const bareInfinitives = restoreInfinitive(normalized);
235
+ for (const inf of bareInfinitives) {
236
+ if (!seen.has(inf) && inf !== normalized) {
237
+ seen.add(inf);
238
+ candidatesWithWeight.push({ candidate: inf, baseLength: normalized.length });
239
+ }
240
+ }
241
+
233
242
  let frontier = [normalized];
234
243
 
235
244
  for (let depth = 0; depth < maxDepth; depth++) {