tdk-api-wrapper 1.4.0 → 1.5.1
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 +43 -14
- package/dist/{chunk-5TYJDVHK.mjs → chunk-NEC5MIQM.mjs} +734 -24
- package/dist/cli.js +872 -29
- package/dist/cli.mjs +185 -6
- package/dist/index.d.mts +119 -1
- package/dist/index.d.ts +119 -1
- package/dist/index.js +738 -25
- package/dist/index.mjs +9 -3
- package/package.json +2 -2
- package/src/cli.ts +191 -5
- package/src/index.ts +1 -1
- package/src/morphology.ts +124 -5
- package/src/tdk.ts +762 -39
- package/src/types.ts +38 -0
- package/test/grammar.test.js +52 -0
- package/test/morphology.test.js +25 -1
- package/test/proofread.test.js +88 -0
- package/test/tools.test.js +51 -0
package/dist/cli.js
CHANGED
|
@@ -357,6 +357,12 @@ var TURKISH_SUFFIXES = [
|
|
|
357
357
|
"s\u0131n",
|
|
358
358
|
"sun",
|
|
359
359
|
"s\xFCn",
|
|
360
|
+
"sen",
|
|
361
|
+
"san",
|
|
362
|
+
"sem",
|
|
363
|
+
"sam",
|
|
364
|
+
"sek",
|
|
365
|
+
"sak",
|
|
360
366
|
"siz",
|
|
361
367
|
"s\u0131z",
|
|
362
368
|
"suz",
|
|
@@ -494,6 +500,43 @@ function restoreVowelDrop(stem) {
|
|
|
494
500
|
}
|
|
495
501
|
return [];
|
|
496
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
|
+
}
|
|
497
540
|
function restoreInfinitive(stem) {
|
|
498
541
|
if (stem.length < 2)
|
|
499
542
|
return [];
|
|
@@ -517,6 +560,13 @@ function getStemCandidates(word2, minStemLength = 2, maxDepth = 4) {
|
|
|
517
560
|
seen.add(apostropheStem);
|
|
518
561
|
}
|
|
519
562
|
}
|
|
563
|
+
const bareInfinitives = restoreInfinitive(normalized);
|
|
564
|
+
for (const inf of bareInfinitives) {
|
|
565
|
+
if (!seen.has(inf) && inf !== normalized) {
|
|
566
|
+
seen.add(inf);
|
|
567
|
+
candidatesWithWeight.push({ candidate: inf, baseLength: normalized.length });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
520
570
|
let frontier = [normalized];
|
|
521
571
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
522
572
|
const nextFrontier = [];
|
|
@@ -526,9 +576,14 @@ function getStemCandidates(word2, minStemLength = 2, maxDepth = 4) {
|
|
|
526
576
|
const stem = current.slice(0, -suffix.length);
|
|
527
577
|
const hardened = restoreConsonantSoftening(stem);
|
|
528
578
|
const vowelDropped = restoreVowelDrop(stem);
|
|
529
|
-
const
|
|
579
|
+
const geminated = restoreGemination(stem);
|
|
580
|
+
const isNarrowingSuffix = suffix.startsWith("yor") || suffix.includes("iyor") || suffix.includes("\u0131yor") || suffix.includes("uyor") || suffix.includes("\xFCyor");
|
|
581
|
+
const isDeYeBuffer = (stem === "di" || stem === "yi") && suffix.startsWith("y");
|
|
582
|
+
const narrowed = isNarrowingSuffix || isDeYeBuffer ? restoreVowelNarrowing(stem) : [];
|
|
583
|
+
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");
|
|
584
|
+
const verbalBases = [stem, ...hardened, ...narrowed];
|
|
530
585
|
const infinitives = verbalBases.flatMap((v) => restoreInfinitive(v));
|
|
531
|
-
const variants = [stem, ...hardened, ...vowelDropped, ...
|
|
586
|
+
const variants = [stem, ...hardened, ...vowelDropped, ...geminated, ...narrowed];
|
|
532
587
|
for (const variant of variants) {
|
|
533
588
|
if (!seen.has(variant) && variant !== normalized) {
|
|
534
589
|
seen.add(variant);
|
|
@@ -536,6 +591,14 @@ function getStemCandidates(word2, minStemLength = 2, maxDepth = 4) {
|
|
|
536
591
|
candidatesWithWeight.push({ candidate: variant, baseLength: stem.length });
|
|
537
592
|
}
|
|
538
593
|
}
|
|
594
|
+
for (const inf of infinitives) {
|
|
595
|
+
if (!seen.has(inf) && inf !== normalized) {
|
|
596
|
+
seen.add(inf);
|
|
597
|
+
nextFrontier.push(inf);
|
|
598
|
+
const weight = isVerbSuffix ? stem.length + 5 : stem.length;
|
|
599
|
+
candidatesWithWeight.push({ candidate: inf, baseLength: weight });
|
|
600
|
+
}
|
|
601
|
+
}
|
|
539
602
|
}
|
|
540
603
|
}
|
|
541
604
|
}
|
|
@@ -544,7 +607,7 @@ function getStemCandidates(word2, minStemLength = 2, maxDepth = 4) {
|
|
|
544
607
|
frontier = nextFrontier;
|
|
545
608
|
}
|
|
546
609
|
candidatesWithWeight.sort((a, b) => b.baseLength - a.baseLength);
|
|
547
|
-
return [...new Set(candidatesWithWeight.map((
|
|
610
|
+
return [...new Set(candidatesWithWeight.map((c2) => c2.candidate))];
|
|
548
611
|
}
|
|
549
612
|
|
|
550
613
|
// src/tdk.ts
|
|
@@ -553,6 +616,159 @@ var path = __toESM(require("path"));
|
|
|
553
616
|
var os = __toESM(require("os"));
|
|
554
617
|
var https = __toESM(require("https"));
|
|
555
618
|
var tls = __toESM(require("tls"));
|
|
619
|
+
var COMMON_MISSPELLINGS = {
|
|
620
|
+
// -şey ile biten ve ayrı yazılması zorunlu söz öbekleri
|
|
621
|
+
her\u015Fey: "her \u015Fey",
|
|
622
|
+
hersey: "her \u015Fey",
|
|
623
|
+
bir\u015Fey: "bir \u015Fey",
|
|
624
|
+
birsey: "bir \u015Fey",
|
|
625
|
+
hi\u00E7bir\u015Fey: "hi\xE7bir \u015Fey",
|
|
626
|
+
hicbirsey: "hi\xE7bir \u015Fey",
|
|
627
|
+
\u00E7ok\u015Fey: "\xE7ok \u015Fey",
|
|
628
|
+
coksey: "\xE7ok \u015Fey",
|
|
629
|
+
\u015Feyler: "\u015Feyler",
|
|
630
|
+
seyler: "\u015Feyler",
|
|
631
|
+
herhangibir\u015Fey: "herhangi bir \u015Fey",
|
|
632
|
+
herhangibirsey: "herhangi bir \u015Fey",
|
|
633
|
+
// Sıkça birleşik yazılan ama ayrı yazılması gereken sözler
|
|
634
|
+
herg\u00FCn: "her g\xFCn",
|
|
635
|
+
hergun: "her g\xFCn",
|
|
636
|
+
herzaman: "her zaman",
|
|
637
|
+
heran: "her an",
|
|
638
|
+
heryer: "her yer",
|
|
639
|
+
herbiri: "her biri",
|
|
640
|
+
pek\u00E7ok: "pek \xE7ok",
|
|
641
|
+
pekcok: "pek \xE7ok",
|
|
642
|
+
pekaz: "pek az",
|
|
643
|
+
yada: "ya da",
|
|
644
|
+
tabiki: "tabii ki",
|
|
645
|
+
tabiiki: "tabii ki",
|
|
646
|
+
sa\u011Fol: "sa\u011F ol",
|
|
647
|
+
sagol: "sa\u011F ol",
|
|
648
|
+
sa\u011Folun: "sa\u011F olun",
|
|
649
|
+
sagolun: "sa\u011F olun",
|
|
650
|
+
ho\u015F\u00E7akal: "ho\u015F\xE7a kal",
|
|
651
|
+
hoscakal: "ho\u015F\xE7a kal",
|
|
652
|
+
ho\u015Fgeldin: "ho\u015F geldin",
|
|
653
|
+
hosgeldin: "ho\u015F geldin",
|
|
654
|
+
ho\u015Fgeldiniz: "ho\u015F geldiniz",
|
|
655
|
+
hosgeldiniz: "ho\u015F geldiniz",
|
|
656
|
+
ho\u015Fbulduk: "ho\u015F bulduk",
|
|
657
|
+
hosbulduk: "ho\u015F bulduk",
|
|
658
|
+
yan\u0131s\u0131ra: "yan\u0131 s\u0131ra",
|
|
659
|
+
yanisira: "yan\u0131 s\u0131ra",
|
|
660
|
+
pe\u015Fis\u0131ra: "pe\u015Fi s\u0131ra",
|
|
661
|
+
pesisira: "pe\u015Fi s\u0131ra",
|
|
662
|
+
ard\u0131s\u0131ra: "ard\u0131 s\u0131ra",
|
|
663
|
+
ardisira: "ard\u0131 s\u0131ra",
|
|
664
|
+
artarda: "art arda",
|
|
665
|
+
y\u00FCzy\u00FCze: "y\xFCz y\xFCze",
|
|
666
|
+
yuzyuze: "y\xFCz y\xFCze",
|
|
667
|
+
elele: "el ele",
|
|
668
|
+
g\u00F6zg\u00F6ze: "g\xF6z g\xF6ze",
|
|
669
|
+
ba\u015Fba\u015Fa: "ba\u015F ba\u015Fa",
|
|
670
|
+
basbasa: "ba\u015F ba\u015Fa",
|
|
671
|
+
yanyana: "yan yana",
|
|
672
|
+
i\u00E7i\u00E7e: "i\xE7 i\xE7e",
|
|
673
|
+
icice: "i\xE7 i\xE7e",
|
|
674
|
+
\u00FCst\u00FCste: "\xFCst \xFCste",
|
|
675
|
+
ustuste: "\xFCst \xFCste",
|
|
676
|
+
altalta: "alt alta",
|
|
677
|
+
\u00F6ns\u00F6z: "\xF6n s\xF6z",
|
|
678
|
+
onsoz: "\xF6n s\xF6z",
|
|
679
|
+
\u00F6nyarg\u0131: "\xF6n yarg\u0131",
|
|
680
|
+
onyargi: "\xF6n yarg\u0131",
|
|
681
|
+
farketmek: "fark etmek",
|
|
682
|
+
farketti: "fark etti",
|
|
683
|
+
farkettim: "fark ettim",
|
|
684
|
+
farkeder: "fark eder",
|
|
685
|
+
farketmez: "fark etmez",
|
|
686
|
+
terketmek: "terk etmek",
|
|
687
|
+
terketti: "terk etti",
|
|
688
|
+
ay\u0131rdetmek: "ay\u0131rt etmek",
|
|
689
|
+
ay\u0131rtetmek: "ay\u0131rt etmek",
|
|
690
|
+
arzetmek: "arz etmek",
|
|
691
|
+
arzederim: "arz ederim",
|
|
692
|
+
varolmak: "var olmak",
|
|
693
|
+
yokolmak: "yok olmak",
|
|
694
|
+
haketmek: "hak etmek",
|
|
695
|
+
haketti: "hak etti",
|
|
696
|
+
hakkaten: "hakikaten",
|
|
697
|
+
hi\u00E7kimse: "hi\xE7 kimse",
|
|
698
|
+
hickimse: "hi\xE7 kimse",
|
|
699
|
+
// Ünlü düşmesi yapılmaması gereken yer bildiren sözler (TDK Kural 15)
|
|
700
|
+
burda: "burada",
|
|
701
|
+
burdan: "buradan",
|
|
702
|
+
\u015Furda: "\u015Furada",
|
|
703
|
+
surda: "\u015Furada",
|
|
704
|
+
\u015Furdan: "\u015Furadan",
|
|
705
|
+
surdan: "\u015Furadan",
|
|
706
|
+
orda: "orada",
|
|
707
|
+
ordan: "oradan",
|
|
708
|
+
i\u00E7erde: "i\xE7eride",
|
|
709
|
+
icerde: "i\xE7eride",
|
|
710
|
+
i\u00E7erden: "i\xE7eriden",
|
|
711
|
+
icerden: "i\xE7eriden",
|
|
712
|
+
d\u0131\u015Farda: "d\u0131\u015Far\u0131da",
|
|
713
|
+
disarda: "d\u0131\u015Far\u0131da",
|
|
714
|
+
d\u0131\u015Fardan: "d\u0131\u015Far\u0131dan",
|
|
715
|
+
disardan: "d\u0131\u015Far\u0131dan",
|
|
716
|
+
yukarda: "yukar\u0131da",
|
|
717
|
+
yukardan: "yukar\u0131dan",
|
|
718
|
+
// Sıkça yanlış yazılan sözcükler
|
|
719
|
+
herkez: "herkes",
|
|
720
|
+
yanl\u0131z: "yaln\u0131z",
|
|
721
|
+
yaln\u0131\u015F: "yanl\u0131\u015F",
|
|
722
|
+
orjinal: "orijinal",
|
|
723
|
+
labaratuar: "laboratuvar",
|
|
724
|
+
laboratuar: "laboratuvar",
|
|
725
|
+
\u015F\u00F6f\u00F6r: "\u015Fof\xF6r",
|
|
726
|
+
sofor: "\u015Fof\xF6r",
|
|
727
|
+
egzos: "egzoz",
|
|
728
|
+
eksoz: "egzoz",
|
|
729
|
+
ekzoz: "egzoz",
|
|
730
|
+
kiprik: "kirpik",
|
|
731
|
+
kirbit: "kibrit",
|
|
732
|
+
klavuz: "k\u0131lavuz",
|
|
733
|
+
k\u0131ravat: "kravat",
|
|
734
|
+
s\u00FCpriz: "s\xFCrpriz",
|
|
735
|
+
supriz: "s\xFCrpriz",
|
|
736
|
+
raslant\u0131: "rastlant\u0131",
|
|
737
|
+
hastahane: "hastane",
|
|
738
|
+
pastahane: "pastane",
|
|
739
|
+
postahane: "postane",
|
|
740
|
+
eczahane: "eczane",
|
|
741
|
+
meyva: "meyve",
|
|
742
|
+
sarm\u0131sak: "sar\u0131msak",
|
|
743
|
+
dinazor: "dinozor",
|
|
744
|
+
pantalon: "pantolon",
|
|
745
|
+
tesbih: "tespih",
|
|
746
|
+
ah\u00E7\u0131: "a\u015F\xE7\u0131",
|
|
747
|
+
matba: "matbaa",
|
|
748
|
+
idda: "iddia",
|
|
749
|
+
iddaa: "iddia",
|
|
750
|
+
muhattap: "muhatap",
|
|
751
|
+
tra\u015F: "t\u0131ra\u015F",
|
|
752
|
+
karn\u0131bahar: "karnabahar",
|
|
753
|
+
kareografi: "koreografi",
|
|
754
|
+
poa\u00E7a: "po\u011Fa\xE7a",
|
|
755
|
+
poha\u00E7a: "po\u011Fa\xE7a",
|
|
756
|
+
\u015Farz: "\u015Farj",
|
|
757
|
+
sarj: "\u015Farj",
|
|
758
|
+
makina: "makine",
|
|
759
|
+
m\u00FCsade: "m\xFCsaade",
|
|
760
|
+
entellekt\u00FCel: "entelekt\xFCel",
|
|
761
|
+
inisiyatif: "inisiyatif",
|
|
762
|
+
insiyatif: "inisiyatif",
|
|
763
|
+
sezeryan: "sezaryen",
|
|
764
|
+
dok\u00FCman: "dok\xFCman",
|
|
765
|
+
d\u00F6k\u00FCman: "dok\xFCman",
|
|
766
|
+
erozyon: "erozyon",
|
|
767
|
+
erizyon: "erozyon",
|
|
768
|
+
anane: "anneanne",
|
|
769
|
+
babaanne: "babaanne"
|
|
770
|
+
};
|
|
771
|
+
var SEY_EXCEPTIONS = /* @__PURE__ */ new Set(["d\xFC\u015Fey", "e\u015Fey", "konsey", "jersey", "\u015Fey"]);
|
|
556
772
|
var TDK = class {
|
|
557
773
|
static BASE_URL = "https://sozluk.gov.tr";
|
|
558
774
|
static AUDIO_API_HOST = "api.sozluk.gov.tr";
|
|
@@ -633,6 +849,10 @@ M71DMi+y1+TRSJVClEMwvA4yL++7q9XZx5r5wBRWB4kQTKH5qyoZnDw7iiuh1lID
|
|
|
633
849
|
yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
634
850
|
-----END CERTIFICATE-----`
|
|
635
851
|
];
|
|
852
|
+
// Configuration
|
|
853
|
+
static defaultTimeoutMs = 8e3;
|
|
854
|
+
static defaultRetries = 1;
|
|
855
|
+
static maxCacheSize = 1e3;
|
|
636
856
|
// Cache Mechanism
|
|
637
857
|
static isCacheEnabled = false;
|
|
638
858
|
static wordCache = /* @__PURE__ */ new Map();
|
|
@@ -640,6 +860,19 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
640
860
|
static autocompleteCache = [];
|
|
641
861
|
static autocompleteSet = /* @__PURE__ */ new Set();
|
|
642
862
|
static stemCache = /* @__PURE__ */ new Map();
|
|
863
|
+
/**
|
|
864
|
+
* Configures global client options such as network timeout, retries, and cache size.
|
|
865
|
+
*/
|
|
866
|
+
static configure(config) {
|
|
867
|
+
if (config.timeoutMs !== void 0)
|
|
868
|
+
this.defaultTimeoutMs = Math.max(100, config.timeoutMs);
|
|
869
|
+
if (config.retries !== void 0)
|
|
870
|
+
this.defaultRetries = Math.max(0, config.retries);
|
|
871
|
+
if (config.cache !== void 0)
|
|
872
|
+
this.enableCache(config.cache);
|
|
873
|
+
if (config.maxCacheSize !== void 0)
|
|
874
|
+
this.maxCacheSize = Math.max(10, config.maxCacheSize);
|
|
875
|
+
}
|
|
643
876
|
/**
|
|
644
877
|
* Enables or disables in-memory caching for API requests.
|
|
645
878
|
*/
|
|
@@ -659,9 +892,50 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
659
892
|
this.autocompleteSet.clear();
|
|
660
893
|
this.stemCache.clear();
|
|
661
894
|
}
|
|
895
|
+
static setBoundedCache(map, key, value) {
|
|
896
|
+
if (map.size >= this.maxCacheSize) {
|
|
897
|
+
const firstKey = map.keys().next().value;
|
|
898
|
+
if (firstKey !== void 0)
|
|
899
|
+
map.delete(firstKey);
|
|
900
|
+
}
|
|
901
|
+
map.set(key, value);
|
|
902
|
+
}
|
|
662
903
|
static delay(ms) {
|
|
663
904
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
664
905
|
}
|
|
906
|
+
/**
|
|
907
|
+
* Internal helper that performs HTTP fetch with timeout and automatic retry on network/5xx errors.
|
|
908
|
+
*/
|
|
909
|
+
static async fetchWithRetry(url, options = {}, retries = this.defaultRetries, timeoutMs = this.defaultTimeoutMs) {
|
|
910
|
+
let lastError;
|
|
911
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
912
|
+
try {
|
|
913
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
914
|
+
const headers = {
|
|
915
|
+
"User-Agent": "TDK-API-Nodejs-Wrapper/1.0",
|
|
916
|
+
...options.headers || {}
|
|
917
|
+
};
|
|
918
|
+
const res = await fetch(url, { ...options, headers, signal });
|
|
919
|
+
if (res.ok || res.status >= 400 && res.status < 500) {
|
|
920
|
+
return res;
|
|
921
|
+
}
|
|
922
|
+
if (attempt < retries) {
|
|
923
|
+
await this.delay(200 * (attempt + 1));
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
return res;
|
|
927
|
+
} catch (err) {
|
|
928
|
+
lastError = err;
|
|
929
|
+
if (attempt < retries) {
|
|
930
|
+
await this.delay(200 * (attempt + 1));
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
throw new TDKNetworkError(`Request to ${url} failed after ${retries + 1} attempts.`, {
|
|
936
|
+
cause: lastError
|
|
937
|
+
});
|
|
938
|
+
}
|
|
665
939
|
/**
|
|
666
940
|
* Fetches detailed information for a given word from the TDK Dictionary.
|
|
667
941
|
*/
|
|
@@ -676,9 +950,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
676
950
|
const url = `${this.BASE_URL}/gts?ara=${encodeURIComponent(cleanWord)}`;
|
|
677
951
|
let response;
|
|
678
952
|
try {
|
|
679
|
-
response = await
|
|
680
|
-
headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" }
|
|
681
|
-
});
|
|
953
|
+
response = await this.fetchWithRetry(url);
|
|
682
954
|
} catch (error) {
|
|
683
955
|
throw new TDKNetworkError("Failed to fetch word from TDK: request failed.", { cause: error });
|
|
684
956
|
}
|
|
@@ -695,12 +967,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
695
967
|
}
|
|
696
968
|
if (!Array.isArray(data) && data && "error" in data) {
|
|
697
969
|
if (this.isCacheEnabled)
|
|
698
|
-
this.wordCache
|
|
970
|
+
this.setBoundedCache(this.wordCache, cleanWord, []);
|
|
699
971
|
return [];
|
|
700
972
|
}
|
|
701
973
|
const results = data;
|
|
702
974
|
if (this.isCacheEnabled) {
|
|
703
|
-
this.wordCache
|
|
975
|
+
this.setBoundedCache(this.wordCache, cleanWord, results);
|
|
704
976
|
}
|
|
705
977
|
return results;
|
|
706
978
|
}
|
|
@@ -827,17 +1099,17 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
827
1099
|
return this.stemCache.get(clean);
|
|
828
1100
|
}
|
|
829
1101
|
if (await this.isHeadword(clean)) {
|
|
830
|
-
this.stemCache
|
|
1102
|
+
this.setBoundedCache(this.stemCache, clean, clean);
|
|
831
1103
|
return clean;
|
|
832
1104
|
}
|
|
833
1105
|
const candidates = getStemCandidates(clean);
|
|
834
1106
|
for (const candidate of candidates) {
|
|
835
1107
|
if (await this.isHeadword(candidate)) {
|
|
836
|
-
this.stemCache
|
|
1108
|
+
this.setBoundedCache(this.stemCache, clean, candidate);
|
|
837
1109
|
return candidate;
|
|
838
1110
|
}
|
|
839
1111
|
}
|
|
840
|
-
this.stemCache
|
|
1112
|
+
this.setBoundedCache(this.stemCache, clean, null);
|
|
841
1113
|
return null;
|
|
842
1114
|
}
|
|
843
1115
|
/**
|
|
@@ -1060,25 +1332,45 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1060
1332
|
* Checks spelling and returns suggestions if wrong.
|
|
1061
1333
|
*/
|
|
1062
1334
|
static async checkSpelling(word2) {
|
|
1335
|
+
if (!word2 || word2.trim() === "") {
|
|
1336
|
+
return { isCorrect: false, word: word2 };
|
|
1337
|
+
}
|
|
1338
|
+
const cleanWord = word2.trim().toLocaleLowerCase("tr-TR");
|
|
1063
1339
|
const results = await this.getWord(word2);
|
|
1064
1340
|
if (results.length > 0) {
|
|
1065
1341
|
return { isCorrect: true, word: word2 };
|
|
1066
1342
|
}
|
|
1343
|
+
if (COMMON_MISSPELLINGS[cleanWord]) {
|
|
1344
|
+
return { isCorrect: false, word: word2, suggestion: COMMON_MISSPELLINGS[cleanWord] };
|
|
1345
|
+
}
|
|
1346
|
+
const seyMatch = cleanWord.match(/^(.+?)(?:şey|sey)([ıiuaeüodekmnl]+)?$/);
|
|
1347
|
+
if (seyMatch && !SEY_EXCEPTIONS.has(cleanWord)) {
|
|
1348
|
+
let prefix = seyMatch[1];
|
|
1349
|
+
const suffix = seyMatch[2] || "";
|
|
1350
|
+
if (prefix === "hicbir")
|
|
1351
|
+
prefix = "hi\xE7bir";
|
|
1352
|
+
if (prefix === "cok")
|
|
1353
|
+
prefix = "\xE7ok";
|
|
1354
|
+
return {
|
|
1355
|
+
isCorrect: false,
|
|
1356
|
+
word: word2,
|
|
1357
|
+
suggestion: `${prefix} \u015Fey${suffix}`
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1067
1360
|
const daily = await this.getDailyContent();
|
|
1068
1361
|
if (daily) {
|
|
1069
|
-
const syydMatch = daily.syyd.find((s) => s.yanliskelime.toLocaleLowerCase("tr-TR") ===
|
|
1362
|
+
const syydMatch = daily.syyd.find((s) => s.yanliskelime.toLocaleLowerCase("tr-TR") === cleanWord);
|
|
1070
1363
|
if (syydMatch) {
|
|
1071
1364
|
return { isCorrect: false, word: word2, suggestion: syydMatch.dogrukelime };
|
|
1072
1365
|
}
|
|
1073
|
-
const mixMatch = daily.karistirma.find((s) => s.yanlis.toLocaleLowerCase("tr-TR") ===
|
|
1366
|
+
const mixMatch = daily.karistirma.find((s) => s.yanlis.toLocaleLowerCase("tr-TR") === cleanWord);
|
|
1074
1367
|
if (mixMatch) {
|
|
1075
1368
|
return { isCorrect: false, word: word2, suggestion: mixMatch.dogru };
|
|
1076
1369
|
}
|
|
1077
1370
|
}
|
|
1078
1371
|
const root = await this.findRoot(word2);
|
|
1079
1372
|
if (root) {
|
|
1080
|
-
const
|
|
1081
|
-
const isInflected = root !== cleanWord2;
|
|
1373
|
+
const isInflected = root !== cleanWord;
|
|
1082
1374
|
return {
|
|
1083
1375
|
isCorrect: true,
|
|
1084
1376
|
word: word2,
|
|
@@ -1089,24 +1381,32 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1089
1381
|
if (this.autocompleteCache.length === 0) {
|
|
1090
1382
|
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
1091
1383
|
}
|
|
1092
|
-
const
|
|
1384
|
+
for (const candidate of this.autocompleteCache) {
|
|
1385
|
+
if (candidate.includes(" ")) {
|
|
1386
|
+
const candidateNoSpace = candidate.replace(/\s+/g, "").toLocaleLowerCase("tr-TR");
|
|
1387
|
+
if (candidateNoSpace === cleanWord) {
|
|
1388
|
+
return { isCorrect: false, word: word2, suggestion: candidate };
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1093
1392
|
let best = null;
|
|
1094
1393
|
for (const candidate of this.autocompleteCache) {
|
|
1095
1394
|
if (candidate.includes(" ") || candidate !== candidate.toLocaleLowerCase("tr-TR"))
|
|
1096
1395
|
continue;
|
|
1097
1396
|
if (Math.abs(candidate.length - cleanWord.length) > 2)
|
|
1098
1397
|
continue;
|
|
1099
|
-
const
|
|
1100
|
-
if (
|
|
1398
|
+
const rawDist = this.damerauLevenshtein(cleanWord, candidate);
|
|
1399
|
+
if (rawDist === 0)
|
|
1101
1400
|
continue;
|
|
1102
1401
|
const firstMismatch = candidate[0] === cleanWord[0] ? 0 : 1;
|
|
1103
1402
|
const lengthMismatch = candidate.length === cleanWord.length ? 0 : 1;
|
|
1403
|
+
const distance = rawDist + (firstMismatch > 0 ? 1.2 : 0);
|
|
1104
1404
|
const better = !best || distance < best.distance || distance === best.distance && firstMismatch < best.firstMismatch || distance === best.distance && firstMismatch === best.firstMismatch && lengthMismatch < best.lengthMismatch;
|
|
1105
1405
|
if (better) {
|
|
1106
|
-
best = { candidate, distance, firstMismatch, lengthMismatch };
|
|
1406
|
+
best = { candidate, distance, rawDist, firstMismatch, lengthMismatch };
|
|
1107
1407
|
}
|
|
1108
1408
|
}
|
|
1109
|
-
if (best && best.
|
|
1409
|
+
if (best && best.rawDist <= 2 && (best.firstMismatch === 0 || best.rawDist <= 1)) {
|
|
1110
1410
|
return { isCorrect: false, word: word2, suggestion: best.candidate };
|
|
1111
1411
|
}
|
|
1112
1412
|
return { isCorrect: false, word: word2 };
|
|
@@ -1513,14 +1813,16 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1513
1813
|
meaningCount: meaningsA.length,
|
|
1514
1814
|
origin: originA,
|
|
1515
1815
|
syllables: this.syllabicate(a),
|
|
1516
|
-
harmony: this.checkVowelHarmony(a)
|
|
1816
|
+
harmony: this.checkVowelHarmony(a),
|
|
1817
|
+
labialHarmony: this.checkLabialHarmony(a)
|
|
1517
1818
|
},
|
|
1518
1819
|
b: {
|
|
1519
1820
|
word: b,
|
|
1520
1821
|
meaningCount: meaningsB.length,
|
|
1521
1822
|
origin: originB,
|
|
1522
1823
|
syllables: this.syllabicate(b),
|
|
1523
|
-
harmony: this.checkVowelHarmony(b)
|
|
1824
|
+
harmony: this.checkVowelHarmony(b),
|
|
1825
|
+
labialHarmony: this.checkLabialHarmony(b)
|
|
1524
1826
|
}
|
|
1525
1827
|
};
|
|
1526
1828
|
}
|
|
@@ -1651,9 +1953,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1651
1953
|
}
|
|
1652
1954
|
/**
|
|
1653
1955
|
* Syllabicates a Turkish word based on general grammar rules.
|
|
1956
|
+
* Handles syllable separation for vowels, single consonants, double consonants,
|
|
1957
|
+
* and western loanword three-consonant clusters (e.g. e-lek-trik, kon-trol, or-kes-tra).
|
|
1654
1958
|
*/
|
|
1655
1959
|
static syllabicate(word2) {
|
|
1656
1960
|
const vowels = /[aeıioöuüAEIİOÖUÜ]/;
|
|
1961
|
+
const ONSET_CLUSTERS = /* @__PURE__ */ new Set(["tr", "pr", "kr", "gr", "br", "fr", "dr", "pl", "kl", "fl", "bl", "gl"]);
|
|
1657
1962
|
const result = [];
|
|
1658
1963
|
let currentSyllable = "";
|
|
1659
1964
|
for (let i = word2.length - 1; i >= 0; i--) {
|
|
@@ -1664,8 +1969,13 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1664
1969
|
currentSyllable = word2[i - 1] + currentSyllable;
|
|
1665
1970
|
i--;
|
|
1666
1971
|
} else if (i - 2 >= 0 && !vowels.test(word2[i - 2])) {
|
|
1667
|
-
|
|
1668
|
-
|
|
1972
|
+
if (i - 3 >= 0 && !vowels.test(word2[i - 3]) && ONSET_CLUSTERS.has((word2[i - 2] + word2[i - 1]).toLowerCase())) {
|
|
1973
|
+
currentSyllable = word2[i - 2] + word2[i - 1] + currentSyllable;
|
|
1974
|
+
i -= 2;
|
|
1975
|
+
} else {
|
|
1976
|
+
currentSyllable = word2[i - 1] + currentSyllable;
|
|
1977
|
+
i--;
|
|
1978
|
+
}
|
|
1669
1979
|
}
|
|
1670
1980
|
}
|
|
1671
1981
|
result.unshift(currentSyllable);
|
|
@@ -1695,12 +2005,375 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1695
2005
|
const hasFront = frontVowels.test(lower);
|
|
1696
2006
|
return !(hasBack && hasFront);
|
|
1697
2007
|
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Checks if a word follows Turkish Minor Vowel Harmony (Küçük Ünlü Uyumu / Labial Harmony).
|
|
2010
|
+
* Rules:
|
|
2011
|
+
* 1. After an unrounded vowel (a, e, ı, i), only unrounded vowels (a, e, ı, i) can follow.
|
|
2012
|
+
* 2. After a rounded vowel (o, ö, u, ü), either an unrounded wide (a, e) or rounded narrow (u, ü) vowel can follow.
|
|
2013
|
+
* Single-syllable words and words with <=1 vowel are considered compliant by convention.
|
|
2014
|
+
*/
|
|
2015
|
+
static checkLabialHarmony(word2) {
|
|
2016
|
+
const lower = word2.toLocaleLowerCase("tr-TR");
|
|
2017
|
+
const vowels = lower.split("").filter((ch) => "ae\u0131io\xF6u\xFC".includes(ch));
|
|
2018
|
+
if (vowels.length <= 1)
|
|
2019
|
+
return true;
|
|
2020
|
+
for (let i = 0; i < vowels.length - 1; i++) {
|
|
2021
|
+
const v1 = vowels[i];
|
|
2022
|
+
const v2 = vowels[i + 1];
|
|
2023
|
+
if ("ae\u0131i".includes(v1)) {
|
|
2024
|
+
if (!"ae\u0131i".includes(v2))
|
|
2025
|
+
return false;
|
|
2026
|
+
} else if ("o\xF6u\xFC".includes(v1)) {
|
|
2027
|
+
if (!"aeu\xFC".includes(v2))
|
|
2028
|
+
return false;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
return true;
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* Searches TDK headwords using a wildcard / pattern string.
|
|
2035
|
+
* Wildcards:
|
|
2036
|
+
* '_' or '?' matches any single character
|
|
2037
|
+
* '*' matches zero or more characters
|
|
2038
|
+
* Example: "k_l_m" matches "kalem", "kelam", "kilim".
|
|
2039
|
+
* Runs in-memory against TDK's 81k headword list.
|
|
2040
|
+
*/
|
|
2041
|
+
static async patternSearch(pattern, options) {
|
|
2042
|
+
if (!pattern || pattern.trim() === "")
|
|
2043
|
+
return [];
|
|
2044
|
+
await this.ensureAutocompleteLoaded();
|
|
2045
|
+
const cleanPattern = pattern.trim().toLocaleLowerCase("tr-TR");
|
|
2046
|
+
const escaped = cleanPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/[_?]/g, "[\\p{L}]").replace(/\*/g, "[\\p{L}]*");
|
|
2047
|
+
const regex = new RegExp(`^${escaped}$`, "u");
|
|
2048
|
+
const max = options?.maxResults ?? 50;
|
|
2049
|
+
const matches = [];
|
|
2050
|
+
for (const headword of this.autocompleteCache) {
|
|
2051
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
2052
|
+
if (regex.test(lower)) {
|
|
2053
|
+
matches.push(headword);
|
|
2054
|
+
if (matches.length >= max)
|
|
2055
|
+
break;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
return matches;
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* Finds headwords in TDK that can be formed from the given letters (anagrams).
|
|
2062
|
+
* If exact-length anagrams exist, they are returned.
|
|
2063
|
+
* If none exist (or exactLength is false), valid sub-anagrams (words using a subset of the letters,
|
|
2064
|
+
* minimum 3 letters) are returned, sorted by length descending.
|
|
2065
|
+
*/
|
|
2066
|
+
static async findAnagrams(letters, options) {
|
|
2067
|
+
if (!letters || letters.trim() === "")
|
|
2068
|
+
return [];
|
|
2069
|
+
await this.ensureAutocompleteLoaded();
|
|
2070
|
+
const clean = letters.trim().toLocaleLowerCase("tr-TR").replace(/[^a-zçğıöşüâîû]/gi, "");
|
|
2071
|
+
if (clean.length === 0)
|
|
2072
|
+
return [];
|
|
2073
|
+
const forceExact = options?.exactLength === true;
|
|
2074
|
+
const max = options?.maxResults ?? 50;
|
|
2075
|
+
const getFrequency = (str) => {
|
|
2076
|
+
const freq = {};
|
|
2077
|
+
for (const ch of str) {
|
|
2078
|
+
freq[ch] = (freq[ch] || 0) + 1;
|
|
2079
|
+
}
|
|
2080
|
+
return freq;
|
|
2081
|
+
};
|
|
2082
|
+
const targetFreq = getFrequency(clean);
|
|
2083
|
+
const exactMatches = [];
|
|
2084
|
+
const subMatches = [];
|
|
2085
|
+
for (const headword of this.autocompleteCache) {
|
|
2086
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
2087
|
+
if (lower.includes(" ") || lower.includes("-"))
|
|
2088
|
+
continue;
|
|
2089
|
+
if (lower.length > clean.length || lower.length < 3)
|
|
2090
|
+
continue;
|
|
2091
|
+
const wordFreq = getFrequency(lower);
|
|
2092
|
+
let isValid = true;
|
|
2093
|
+
for (const [ch, count] of Object.entries(wordFreq)) {
|
|
2094
|
+
if (!targetFreq[ch] || targetFreq[ch] < count) {
|
|
2095
|
+
isValid = false;
|
|
2096
|
+
break;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
if (isValid && lower !== clean) {
|
|
2100
|
+
if (lower.length === clean.length) {
|
|
2101
|
+
exactMatches.push(headword);
|
|
2102
|
+
} else {
|
|
2103
|
+
subMatches.push(headword);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
if (exactMatches.length > 0 || forceExact) {
|
|
2108
|
+
return exactMatches.slice(0, max);
|
|
2109
|
+
}
|
|
2110
|
+
subMatches.sort((a, b) => b.length - a.length || a.localeCompare(b, "tr-TR"));
|
|
2111
|
+
return subMatches.slice(0, max);
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Finds words in TDK that rhyme with the given word (sharing the same ending suffix/letters).
|
|
2115
|
+
* @param word The target word
|
|
2116
|
+
* @param options.minLetters Minimum number of ending characters that must match (default: 3)
|
|
2117
|
+
* @param options.maxResults Maximum number of rhyme results to return (default: 50)
|
|
2118
|
+
*/
|
|
2119
|
+
static async findRhymes(word2, options) {
|
|
2120
|
+
if (!word2 || word2.trim() === "")
|
|
2121
|
+
return [];
|
|
2122
|
+
await this.ensureAutocompleteLoaded();
|
|
2123
|
+
const clean = word2.trim().toLocaleLowerCase("tr-TR");
|
|
2124
|
+
const minLetters = Math.min(options?.minLetters ?? 3, clean.length);
|
|
2125
|
+
const max = options?.maxResults ?? 50;
|
|
2126
|
+
const suffix = clean.slice(-minLetters);
|
|
2127
|
+
const results = [];
|
|
2128
|
+
for (const headword of this.autocompleteCache) {
|
|
2129
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
2130
|
+
if (lower !== clean && lower.endsWith(suffix) && !lower.includes(" ")) {
|
|
2131
|
+
results.push(headword);
|
|
2132
|
+
if (results.length >= max)
|
|
2133
|
+
break;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
return results;
|
|
2137
|
+
}
|
|
2138
|
+
/**
|
|
2139
|
+
* Performs comprehensive spelling, grammar, and syntax proofreading on a Turkish text.
|
|
2140
|
+
* Detects:
|
|
2141
|
+
* 1. Conjunction 'da/de' erroneously joined to verbs or words (e.g. "gitsende" -> "gitsen de")
|
|
2142
|
+
* 2. Conjunction 'ki' erroneously joined to verbs (e.g. "gördümki" -> "gördüm ki"), respecting SOMBAHÇEMİ exceptions
|
|
2143
|
+
* 3. Question particle 'mi/mı/mu/mü' erroneously joined to words (e.g. "geldimi" -> "geldi mi")
|
|
2144
|
+
* 4. Misspelled words with dictionary suggestions (via edit-distance & morphology)
|
|
2145
|
+
*/
|
|
2146
|
+
static async proofread(text) {
|
|
2147
|
+
if (!text || text.trim() === "") {
|
|
2148
|
+
return { text: text || "", issues: [], isCorrect: true };
|
|
2149
|
+
}
|
|
2150
|
+
await this.ensureAutocompleteLoaded();
|
|
2151
|
+
const issues = [];
|
|
2152
|
+
const SOMBAHCEMI = /* @__PURE__ */ new Set([
|
|
2153
|
+
"sanki",
|
|
2154
|
+
"oysaki",
|
|
2155
|
+
"mademki",
|
|
2156
|
+
"belki",
|
|
2157
|
+
"halbuki",
|
|
2158
|
+
"\xE7\xFCnk\xFC",
|
|
2159
|
+
"me\u011Ferki",
|
|
2160
|
+
"illaki"
|
|
2161
|
+
]);
|
|
2162
|
+
const PHRASE_MISTAKES = [
|
|
2163
|
+
{
|
|
2164
|
+
regex: /\bhiç\s+bir\b/gi,
|
|
2165
|
+
suggestion: "hi\xE7bir",
|
|
2166
|
+
message: "'hi\xE7bir' belgisiz s\u0131fat\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2167
|
+
type: "spelling"
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
regex: /\bbir\s+çok\b/gi,
|
|
2171
|
+
suggestion: "bir\xE7ok",
|
|
2172
|
+
message: "'bir\xE7ok' belgisiz s\u0131fat\u0131/zamiri biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2173
|
+
type: "spelling"
|
|
2174
|
+
},
|
|
2175
|
+
{
|
|
2176
|
+
regex: /\bbir\s+kaç\b/gi,
|
|
2177
|
+
suggestion: "birka\xE7",
|
|
2178
|
+
message: "'birka\xE7' belgisiz s\u0131fat\u0131/zamiri biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2179
|
+
type: "spelling"
|
|
2180
|
+
},
|
|
2181
|
+
{
|
|
2182
|
+
regex: /\bbir\s+az\b/gi,
|
|
2183
|
+
suggestion: "biraz",
|
|
2184
|
+
message: "'biraz' s\xF6zc\xFC\u011F\xFC biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2185
|
+
type: "spelling"
|
|
2186
|
+
},
|
|
2187
|
+
{
|
|
2188
|
+
regex: /\bher\s+hangi\b/gi,
|
|
2189
|
+
suggestion: "herhangi",
|
|
2190
|
+
message: "'herhangi' s\xF6zc\xFC\u011F\xFC biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2191
|
+
type: "spelling"
|
|
2192
|
+
},
|
|
2193
|
+
{
|
|
2194
|
+
regex: /\bgit\s+gide\b/gi,
|
|
2195
|
+
suggestion: "gitgide",
|
|
2196
|
+
message: "'gitgide' zarf\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2197
|
+
type: "spelling"
|
|
2198
|
+
},
|
|
2199
|
+
{
|
|
2200
|
+
regex: /\bbirden\s+bire\b/gi,
|
|
2201
|
+
suggestion: "birdenbire",
|
|
2202
|
+
message: "'birdenbire' zarf\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2203
|
+
type: "spelling"
|
|
2204
|
+
},
|
|
2205
|
+
{
|
|
2206
|
+
regex: /\brast\s+gele\b/gi,
|
|
2207
|
+
suggestion: "rastgele",
|
|
2208
|
+
message: "'rastgele' zarf\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2209
|
+
type: "spelling"
|
|
2210
|
+
}
|
|
2211
|
+
];
|
|
2212
|
+
const coveredRanges = [];
|
|
2213
|
+
for (const pm of PHRASE_MISTAKES) {
|
|
2214
|
+
let pmMatch;
|
|
2215
|
+
while ((pmMatch = pm.regex.exec(text)) !== null) {
|
|
2216
|
+
const start = pmMatch.index;
|
|
2217
|
+
const end = start + pmMatch[0].length;
|
|
2218
|
+
coveredRanges.push({ start, end });
|
|
2219
|
+
issues.push({
|
|
2220
|
+
type: pm.type,
|
|
2221
|
+
word: pmMatch[0],
|
|
2222
|
+
startIndex: start,
|
|
2223
|
+
endIndex: end,
|
|
2224
|
+
suggestion: pm.suggestion,
|
|
2225
|
+
message: pm.message
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
const tokenRegex = /[\p{L}0-9'’]+/gu;
|
|
2230
|
+
let match;
|
|
2231
|
+
while ((match = tokenRegex.exec(text)) !== null) {
|
|
2232
|
+
const rawWord = match[0];
|
|
2233
|
+
const startIndex = match.index;
|
|
2234
|
+
const endIndex = startIndex + rawWord.length;
|
|
2235
|
+
const lower = rawWord.toLocaleLowerCase("tr-TR");
|
|
2236
|
+
if (/^\d+$/.test(lower))
|
|
2237
|
+
continue;
|
|
2238
|
+
if (coveredRanges.some((r) => startIndex >= r.start && endIndex <= r.end))
|
|
2239
|
+
continue;
|
|
2240
|
+
let flagged = false;
|
|
2241
|
+
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)?)$/);
|
|
2242
|
+
if (questionMatch) {
|
|
2243
|
+
const base = questionMatch[1];
|
|
2244
|
+
const particle = questionMatch[2];
|
|
2245
|
+
if (base.length >= 2 && (await this.isHeadword(base) || await this.findRoot(base) !== null)) {
|
|
2246
|
+
if (!await this.isHeadword(lower)) {
|
|
2247
|
+
issues.push({
|
|
2248
|
+
type: "question_particle",
|
|
2249
|
+
word: rawWord,
|
|
2250
|
+
startIndex,
|
|
2251
|
+
endIndex,
|
|
2252
|
+
suggestion: `${base} ${particle}`,
|
|
2253
|
+
message: `'${particle}' soru eki kendinden \xF6nceki kelimeden ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
|
|
2254
|
+
});
|
|
2255
|
+
flagged = true;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
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;
|
|
2260
|
+
if (!flagged && lower.endsWith("ki") && lower.length > 3) {
|
|
2261
|
+
const base = lower.slice(0, -2);
|
|
2262
|
+
if (!SOMBAHCEMI.has(lower)) {
|
|
2263
|
+
if (!await this.isHeadword(lower)) {
|
|
2264
|
+
const root = await this.findRoot(base);
|
|
2265
|
+
const isVerb = (base === "demek" || base === "kald\u0131" || base === "yeter" || base === "bilmem" || VERB_CONJUGATION_REGEX.test(base)) && (root ? root.endsWith("mek") || root.endsWith("mak") : true);
|
|
2266
|
+
if (isVerb) {
|
|
2267
|
+
issues.push({
|
|
2268
|
+
type: "conjunction_ki",
|
|
2269
|
+
word: rawWord,
|
|
2270
|
+
startIndex,
|
|
2271
|
+
endIndex,
|
|
2272
|
+
suggestion: `${base} ki`,
|
|
2273
|
+
message: `'ki' ba\u011Flac\u0131 ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
|
|
2274
|
+
});
|
|
2275
|
+
flagged = true;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
if (!flagged && (lower.endsWith("de") || lower.endsWith("da") || lower.endsWith("te") || lower.endsWith("ta")) && lower.length > 3) {
|
|
2281
|
+
const base = lower.slice(0, -2);
|
|
2282
|
+
const ending = lower.slice(-2);
|
|
2283
|
+
if (!await this.isHeadword(lower)) {
|
|
2284
|
+
const root = await this.findRoot(base);
|
|
2285
|
+
const isVerb = VERB_CONJUGATION_REGEX.test(base) && (root ? root.endsWith("mek") || root.endsWith("mak") : false);
|
|
2286
|
+
if (isVerb) {
|
|
2287
|
+
const correctEnding = ending.startsWith("t") ? ending === "te" ? "de" : "da" : ending;
|
|
2288
|
+
issues.push({
|
|
2289
|
+
type: "conjunction_da",
|
|
2290
|
+
word: rawWord,
|
|
2291
|
+
startIndex,
|
|
2292
|
+
endIndex,
|
|
2293
|
+
suggestion: `${base} ${correctEnding}`,
|
|
2294
|
+
message: `'da/de' ba\u011Flac\u0131 fiillerden sonra her zaman ayr\u0131 yaz\u0131l\u0131r (ba\u011Fla\xE7 olan da/de sertle\u015Fmez).`
|
|
2295
|
+
});
|
|
2296
|
+
flagged = true;
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
const seyMatch = lower.match(/^(.+?)(?:şey|sey)([ıiuaeüodekmnl]+)?$/);
|
|
2301
|
+
if (!flagged && seyMatch && !SEY_EXCEPTIONS.has(lower)) {
|
|
2302
|
+
let prefix = seyMatch[1];
|
|
2303
|
+
const suffix = seyMatch[2] || "";
|
|
2304
|
+
if (prefix === "hicbir")
|
|
2305
|
+
prefix = "hi\xE7bir";
|
|
2306
|
+
if (prefix === "cok")
|
|
2307
|
+
prefix = "\xE7ok";
|
|
2308
|
+
issues.push({
|
|
2309
|
+
type: "spelling",
|
|
2310
|
+
word: rawWord,
|
|
2311
|
+
startIndex,
|
|
2312
|
+
endIndex,
|
|
2313
|
+
suggestion: `${prefix} \u015Fey${suffix}`,
|
|
2314
|
+
message: "'\u015Fey' s\xF6zc\xFC\u011F\xFC kendinden \xF6nceki kelimeden ayr\u0131 yaz\u0131lmal\u0131d\u0131r."
|
|
2315
|
+
});
|
|
2316
|
+
flagged = true;
|
|
2317
|
+
}
|
|
2318
|
+
if (!flagged && lower === "yada") {
|
|
2319
|
+
issues.push({
|
|
2320
|
+
type: "spelling",
|
|
2321
|
+
word: rawWord,
|
|
2322
|
+
startIndex,
|
|
2323
|
+
endIndex,
|
|
2324
|
+
suggestion: "ya da",
|
|
2325
|
+
message: "'ya da' ba\u011Flac\u0131 her zaman ayr\u0131 yaz\u0131l\u0131r."
|
|
2326
|
+
});
|
|
2327
|
+
flagged = true;
|
|
2328
|
+
}
|
|
2329
|
+
if (!flagged && (lower === "burda" || lower === "\u015Furda" || lower === "surda" || lower === "orda" || lower === "i\xE7erde" || lower === "icerde" || lower === "d\u0131\u015Farda" || lower === "disarda" || lower === "yukarda")) {
|
|
2330
|
+
const correct = COMMON_MISSPELLINGS[lower] || lower;
|
|
2331
|
+
issues.push({
|
|
2332
|
+
type: "spelling",
|
|
2333
|
+
word: rawWord,
|
|
2334
|
+
startIndex,
|
|
2335
|
+
endIndex,
|
|
2336
|
+
suggestion: correct,
|
|
2337
|
+
message: `'${rawWord}' s\xF6zc\xFC\u011F\xFCnde \xFCnl\xFC d\xFC\u015Fmesi yap\u0131lmaz.`
|
|
2338
|
+
});
|
|
2339
|
+
flagged = true;
|
|
2340
|
+
}
|
|
2341
|
+
if (!flagged) {
|
|
2342
|
+
const check = await this.checkSpelling(rawWord);
|
|
2343
|
+
if (!check.isCorrect) {
|
|
2344
|
+
issues.push({
|
|
2345
|
+
type: "spelling",
|
|
2346
|
+
word: rawWord,
|
|
2347
|
+
startIndex,
|
|
2348
|
+
endIndex,
|
|
2349
|
+
suggestion: check.suggestion,
|
|
2350
|
+
message: check.suggestion ? `'${rawWord}' yanl\u0131\u015F yaz\u0131lm\u0131\u015F olabilir.` : `'${rawWord}' s\xF6zl\xFCkte bulunamad\u0131.`
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
issues.sort((a, b) => a.startIndex - b.startIndex);
|
|
2356
|
+
return {
|
|
2357
|
+
text,
|
|
2358
|
+
issues,
|
|
2359
|
+
isCorrect: issues.length === 0
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
1698
2362
|
};
|
|
1699
2363
|
|
|
1700
2364
|
// src/cli.ts
|
|
1701
2365
|
var rawArgs = process.argv.slice(2);
|
|
1702
2366
|
var jsonMode = rawArgs.includes("--json");
|
|
1703
2367
|
var args = rawArgs.filter((a) => a !== "--json");
|
|
2368
|
+
var isColor = !jsonMode && Boolean(process.stdout.isTTY);
|
|
2369
|
+
var c = {
|
|
2370
|
+
bold: (s) => isColor ? `\x1B[1m${s}\x1B[0m` : s,
|
|
2371
|
+
dim: (s) => isColor ? `\x1B[2m${s}\x1B[0m` : s,
|
|
2372
|
+
green: (s) => isColor ? `\x1B[32m${s}\x1B[0m` : s,
|
|
2373
|
+
yellow: (s) => isColor ? `\x1B[33m${s}\x1B[0m` : s,
|
|
2374
|
+
cyan: (s) => isColor ? `\x1B[36m${s}\x1B[0m` : s,
|
|
2375
|
+
red: (s) => isColor ? `\x1B[31m${s}\x1B[0m` : s
|
|
2376
|
+
};
|
|
1704
2377
|
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
1705
2378
|
"ara",
|
|
1706
2379
|
"anlam",
|
|
@@ -1708,6 +2381,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
1708
2381
|
"ornek",
|
|
1709
2382
|
"hece",
|
|
1710
2383
|
"uyum",
|
|
2384
|
+
"kucukuyum",
|
|
1711
2385
|
"yazim",
|
|
1712
2386
|
"kok",
|
|
1713
2387
|
"stem",
|
|
@@ -1722,13 +2396,21 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
1722
2396
|
"karsilastir",
|
|
1723
2397
|
"analiz",
|
|
1724
2398
|
"oneri",
|
|
2399
|
+
"bulmaca",
|
|
2400
|
+
"pattern",
|
|
2401
|
+
"anagram",
|
|
2402
|
+
"kafiye",
|
|
2403
|
+
"rhyme",
|
|
2404
|
+
"denetle",
|
|
2405
|
+
"proofread",
|
|
2406
|
+
"repl",
|
|
1725
2407
|
"kubbealti",
|
|
1726
2408
|
"nisanyan",
|
|
1727
2409
|
"viki"
|
|
1728
2410
|
]);
|
|
1729
2411
|
var command = args[0];
|
|
1730
2412
|
var word = args.slice(1).join(" ");
|
|
1731
|
-
if (command && !KNOWN_COMMANDS.has(command) && command !== "--help" && command !== "-h") {
|
|
2413
|
+
if (command && !KNOWN_COMMANDS.has(command) && command !== "--help" && command !== "-h" && command !== "--version" && command !== "-v") {
|
|
1732
2414
|
word = args.join(" ");
|
|
1733
2415
|
command = "anlam";
|
|
1734
2416
|
}
|
|
@@ -1743,17 +2425,97 @@ function printError(message) {
|
|
|
1743
2425
|
if (jsonMode) {
|
|
1744
2426
|
console.log(JSON.stringify({ error: message }));
|
|
1745
2427
|
} else {
|
|
1746
|
-
console.log(`Hata: ${message}`);
|
|
2428
|
+
console.log(c.red(`Hata: ${message}`));
|
|
1747
2429
|
}
|
|
1748
2430
|
}
|
|
2431
|
+
async function startRepl() {
|
|
2432
|
+
const readline = await import("readline");
|
|
2433
|
+
const rl = readline.createInterface({
|
|
2434
|
+
input: process.stdin,
|
|
2435
|
+
output: process.stdout,
|
|
2436
|
+
prompt: c.cyan("tdk> ")
|
|
2437
|
+
});
|
|
2438
|
+
console.log(c.bold("TDK \u0130nteraktif S\xF6zl\xFCk Kabu\u011Fu (\xC7\u0131kmak i\xE7in 'exit' veya Ctrl+C)"));
|
|
2439
|
+
console.log(c.dim("Komutlar: ara <kelime>, hece <kelime>, bulmaca <desen>, denetle <metin> veya do\u011Frudan kelime"));
|
|
2440
|
+
rl.prompt();
|
|
2441
|
+
rl.on("line", async (line) => {
|
|
2442
|
+
const trimmed = line.trim();
|
|
2443
|
+
if (!trimmed) {
|
|
2444
|
+
rl.prompt();
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
if (trimmed === "exit" || trimmed === "quit" || trimmed === ".exit") {
|
|
2448
|
+
rl.close();
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
const parts = trimmed.split(/\s+/);
|
|
2452
|
+
let subCmd = parts[0].toLowerCase();
|
|
2453
|
+
let subArg = parts.slice(1).join(" ");
|
|
2454
|
+
if (!KNOWN_COMMANDS.has(subCmd)) {
|
|
2455
|
+
subArg = trimmed;
|
|
2456
|
+
subCmd = "anlam";
|
|
2457
|
+
}
|
|
2458
|
+
try {
|
|
2459
|
+
if (subCmd === "ara" || subCmd === "anlam") {
|
|
2460
|
+
const meanings = await TDK.getMeanings(subArg);
|
|
2461
|
+
if (meanings.length === 0)
|
|
2462
|
+
console.log(c.dim("Sonu\xE7 bulunamad\u0131."));
|
|
2463
|
+
else
|
|
2464
|
+
meanings.forEach((m, i) => console.log(`${i + 1}. ${c.green(m)}`));
|
|
2465
|
+
} else if (subCmd === "koken") {
|
|
2466
|
+
const origin = await TDK.getOrigin(subArg);
|
|
2467
|
+
console.log(`K\xF6ken: ${c.cyan(origin || "Bilinmiyor")}`);
|
|
2468
|
+
} else if (subCmd === "hece") {
|
|
2469
|
+
const s = TDK.syllabicate(subArg);
|
|
2470
|
+
console.log(`Heceler: ${c.yellow(s.join("-"))}`);
|
|
2471
|
+
} else if (subCmd === "uyum") {
|
|
2472
|
+
const h = TDK.checkVowelHarmony(subArg);
|
|
2473
|
+
console.log(`B\xFCy\xFCk \xDCnl\xFC Uyumu: ${h ? c.green("Uyar") : c.red("Uymaz")}`);
|
|
2474
|
+
} else if (subCmd === "kucukuyum") {
|
|
2475
|
+
const h = TDK.checkLabialHarmony(subArg);
|
|
2476
|
+
console.log(`K\xFC\xE7\xFCk \xDCnl\xFC Uyumu: ${h ? c.green("Uyar") : c.red("Uymaz")}`);
|
|
2477
|
+
} else if (subCmd === "bulmaca" || subCmd === "pattern") {
|
|
2478
|
+
const matches = await TDK.patternSearch(subArg);
|
|
2479
|
+
console.log(matches.slice(0, 15).join(", "));
|
|
2480
|
+
} else if (subCmd === "denetle" || subCmd === "proofread") {
|
|
2481
|
+
const res = await TDK.proofread(subArg);
|
|
2482
|
+
if (res.isCorrect)
|
|
2483
|
+
console.log(c.green("\u2713 Sorun bulunamad\u0131."));
|
|
2484
|
+
else
|
|
2485
|
+
res.issues.forEach((iss) => console.log(`- ${c.yellow(iss.word)}: ${iss.message}${iss.suggestion ? " -> " + c.green(iss.suggestion) : ""}`));
|
|
2486
|
+
} else {
|
|
2487
|
+
console.log(c.dim("\xD6rnek komutlar: 'ara kalem', 'hece elektrik', 'bulmaca k_l_m', 'denetle Bug\xFCn evdeyim'"));
|
|
2488
|
+
}
|
|
2489
|
+
} catch (e) {
|
|
2490
|
+
console.log(c.red(`Hata: ${e?.message || e}`));
|
|
2491
|
+
}
|
|
2492
|
+
rl.prompt();
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
1749
2495
|
async function run() {
|
|
1750
|
-
if (!command
|
|
2496
|
+
if (!command) {
|
|
2497
|
+
if (process.stdin.isTTY) {
|
|
2498
|
+
await startRepl();
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
console.log("Kullan\u0131m: tdk [komut] <kelime> [--json]");
|
|
2502
|
+
console.log(
|
|
2503
|
+
"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"
|
|
2504
|
+
);
|
|
2505
|
+
console.log("Not: Komut belirtilmezse do\u011Frudan kelime anlam\u0131 aran\u0131r (\xF6rn: tdk selam)");
|
|
2506
|
+
process.exit(1);
|
|
2507
|
+
}
|
|
2508
|
+
if (command === "--version" || command === "-v") {
|
|
2509
|
+
console.log("tdk-api-wrapper v1.5.1");
|
|
2510
|
+
process.exit(0);
|
|
2511
|
+
}
|
|
2512
|
+
if (command === "--help" || command === "-h") {
|
|
1751
2513
|
console.log("Kullan\u0131m: tdk [komut] <kelime> [--json]");
|
|
1752
2514
|
console.log(
|
|
1753
|
-
"Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim, kok, deyim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, kubbealti, nisanyan, viki"
|
|
2515
|
+
"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"
|
|
1754
2516
|
);
|
|
1755
2517
|
console.log("Not: Komut belirtilmezse do\u011Frudan kelime anlam\u0131 aran\u0131r (\xF6rn: tdk selam)");
|
|
1756
|
-
process.exit(
|
|
2518
|
+
process.exit(0);
|
|
1757
2519
|
}
|
|
1758
2520
|
TDK.enableCache(false);
|
|
1759
2521
|
try {
|
|
@@ -1812,6 +2574,17 @@ async function run() {
|
|
|
1812
2574
|
);
|
|
1813
2575
|
break;
|
|
1814
2576
|
}
|
|
2577
|
+
case "kucukuyum":
|
|
2578
|
+
case "labial": {
|
|
2579
|
+
if (!word)
|
|
2580
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
2581
|
+
const isHarmony = TDK.checkLabialHarmony(word);
|
|
2582
|
+
printResult(
|
|
2583
|
+
{ word, labialHarmony: isHarmony },
|
|
2584
|
+
() => console.log(`K\xFC\xE7\xFCk \xDCnl\xFC Uyumu: ${isHarmony ? "Uyar" : "Uymaz"}`)
|
|
2585
|
+
);
|
|
2586
|
+
break;
|
|
2587
|
+
}
|
|
1815
2588
|
case "yazim": {
|
|
1816
2589
|
if (!word)
|
|
1817
2590
|
throw new Error("Kelime belirtmelisiniz.");
|
|
@@ -1987,6 +2760,76 @@ async function run() {
|
|
|
1987
2760
|
});
|
|
1988
2761
|
break;
|
|
1989
2762
|
}
|
|
2763
|
+
case "bulmaca":
|
|
2764
|
+
case "pattern": {
|
|
2765
|
+
if (!word)
|
|
2766
|
+
throw new Error("Desen belirtmelisiniz (\xF6rn: k_l_m).");
|
|
2767
|
+
const matches = await TDK.patternSearch(word);
|
|
2768
|
+
printResult(matches, () => {
|
|
2769
|
+
if (matches.length === 0) {
|
|
2770
|
+
console.log("E\u015Fle\u015Fen kelime bulunamad\u0131.");
|
|
2771
|
+
} else {
|
|
2772
|
+
console.log(c.bold(`Bulunan Kelimeler (${matches.length}):`));
|
|
2773
|
+
matches.forEach((m, i) => console.log(`${i + 1}. ${c.cyan(m)}`));
|
|
2774
|
+
}
|
|
2775
|
+
});
|
|
2776
|
+
break;
|
|
2777
|
+
}
|
|
2778
|
+
case "anagram": {
|
|
2779
|
+
if (!word)
|
|
2780
|
+
throw new Error("Harfler belirtmelisiniz.");
|
|
2781
|
+
const anagrams = await TDK.findAnagrams(word);
|
|
2782
|
+
printResult(anagrams, () => {
|
|
2783
|
+
if (anagrams.length === 0) {
|
|
2784
|
+
console.log("Anagram veya bu harflerle t\xFCretilebilecek kelime bulunamad\u0131.");
|
|
2785
|
+
} else {
|
|
2786
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR").replace(/[^a-zçğıöşüâîû]/gi, "");
|
|
2787
|
+
const hasExact = anagrams.some((a) => a.length === clean.length);
|
|
2788
|
+
const title = hasExact ? `Anagramlar (${anagrams.length}):` : `Birebir anagram bulunamad\u0131. Bu harflerle t\xFCretilen kelimeler (${anagrams.length}):`;
|
|
2789
|
+
console.log(c.bold(title));
|
|
2790
|
+
anagrams.forEach((a, i) => console.log(`${i + 1}. ${c.green(a)} ${c.dim(`(${a.length} harf)`)}`));
|
|
2791
|
+
}
|
|
2792
|
+
});
|
|
2793
|
+
break;
|
|
2794
|
+
}
|
|
2795
|
+
case "kafiye":
|
|
2796
|
+
case "rhyme": {
|
|
2797
|
+
if (!word)
|
|
2798
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
2799
|
+
const rhymes = await TDK.findRhymes(word);
|
|
2800
|
+
printResult(rhymes, () => {
|
|
2801
|
+
if (rhymes.length === 0) {
|
|
2802
|
+
console.log("Kafiye bulunamad\u0131.");
|
|
2803
|
+
} else {
|
|
2804
|
+
console.log(c.bold(`Kafiyeli Kelimeler (${rhymes.length}):`));
|
|
2805
|
+
rhymes.forEach((r, i) => console.log(`${i + 1}. ${c.yellow(r)}`));
|
|
2806
|
+
}
|
|
2807
|
+
});
|
|
2808
|
+
break;
|
|
2809
|
+
}
|
|
2810
|
+
case "denetle":
|
|
2811
|
+
case "proofread": {
|
|
2812
|
+
if (!word)
|
|
2813
|
+
throw new Error("Metin belirtmelisiniz.");
|
|
2814
|
+
const result = await TDK.proofread(word);
|
|
2815
|
+
printResult(result, () => {
|
|
2816
|
+
if (result.isCorrect) {
|
|
2817
|
+
console.log(c.green("\u2713 Metinde imla veya ba\u011Fla\xE7 hatas\u0131 tespit edilmedi."));
|
|
2818
|
+
} else {
|
|
2819
|
+
console.log(c.bold(c.red(`Metinde ${result.issues.length} olas\u0131 sorun tespit edildi:`)));
|
|
2820
|
+
result.issues.forEach((issue, i) => {
|
|
2821
|
+
const label = c.yellow(`[${issue.type}]`);
|
|
2822
|
+
const sug = issue.suggestion ? c.green(` -> \xD6neri: ${issue.suggestion}`) : "";
|
|
2823
|
+
console.log(`${i + 1}. ${label} "${c.bold(issue.word)}": ${issue.message}${sug}`);
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
break;
|
|
2828
|
+
}
|
|
2829
|
+
case "repl": {
|
|
2830
|
+
await startRepl();
|
|
2831
|
+
break;
|
|
2832
|
+
}
|
|
1990
2833
|
case "kubbealti": {
|
|
1991
2834
|
if (!word)
|
|
1992
2835
|
throw new Error("Kelime belirtmelisiniz.");
|