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/index.js
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
TDK: () => TDK,
|
|
34
|
+
TDKClient: () => TDKClient,
|
|
34
35
|
TDKError: () => TDKError,
|
|
35
36
|
TDKNetworkError: () => TDKNetworkError,
|
|
36
37
|
TDKValidationError: () => TDKValidationError,
|
|
@@ -39,8 +40,10 @@ __export(src_exports, {
|
|
|
39
40
|
getStemCandidates: () => getStemCandidates,
|
|
40
41
|
isVowel: () => isVowel,
|
|
41
42
|
restoreConsonantSoftening: () => restoreConsonantSoftening,
|
|
43
|
+
restoreGemination: () => restoreGemination,
|
|
42
44
|
restoreInfinitive: () => restoreInfinitive,
|
|
43
|
-
restoreVowelDrop: () => restoreVowelDrop
|
|
45
|
+
restoreVowelDrop: () => restoreVowelDrop,
|
|
46
|
+
restoreVowelNarrowing: () => restoreVowelNarrowing
|
|
44
47
|
});
|
|
45
48
|
module.exports = __toCommonJS(src_exports);
|
|
46
49
|
|
|
@@ -378,6 +381,12 @@ var TURKISH_SUFFIXES = [
|
|
|
378
381
|
"s\u0131n",
|
|
379
382
|
"sun",
|
|
380
383
|
"s\xFCn",
|
|
384
|
+
"sen",
|
|
385
|
+
"san",
|
|
386
|
+
"sem",
|
|
387
|
+
"sam",
|
|
388
|
+
"sek",
|
|
389
|
+
"sak",
|
|
381
390
|
"siz",
|
|
382
391
|
"s\u0131z",
|
|
383
392
|
"suz",
|
|
@@ -515,6 +524,43 @@ function restoreVowelDrop(stem) {
|
|
|
515
524
|
}
|
|
516
525
|
return [];
|
|
517
526
|
}
|
|
527
|
+
function restoreGemination(stem) {
|
|
528
|
+
if (stem.length < 3)
|
|
529
|
+
return [];
|
|
530
|
+
const c1 = stem[stem.length - 2];
|
|
531
|
+
const c2 = stem[stem.length - 1];
|
|
532
|
+
if (c1 === c2 && !isVowel(c1)) {
|
|
533
|
+
const single = stem.slice(0, -1);
|
|
534
|
+
const hardened = restoreConsonantSoftening(single);
|
|
535
|
+
return [single, ...hardened];
|
|
536
|
+
}
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
function restoreVowelNarrowing(stem) {
|
|
540
|
+
if (stem.length < 2)
|
|
541
|
+
return [];
|
|
542
|
+
if (stem === "di")
|
|
543
|
+
return ["de"];
|
|
544
|
+
if (stem === "yi")
|
|
545
|
+
return ["ye"];
|
|
546
|
+
const lastChar = stem[stem.length - 1];
|
|
547
|
+
const isLastNarrow = "\u0131iu\xFC".includes(lastChar);
|
|
548
|
+
if (isLastNarrow) {
|
|
549
|
+
const vowelsInBase = stem.slice(0, -1).split("").filter(isVowel);
|
|
550
|
+
const lastVowel = vowelsInBase.length > 0 ? vowelsInBase[vowelsInBase.length - 1] : lastChar;
|
|
551
|
+
const widened = "a\u0131ou".includes(lastVowel) ? "a" : "e";
|
|
552
|
+
return [stem.slice(0, -1) + widened];
|
|
553
|
+
}
|
|
554
|
+
if (!isVowel(lastChar)) {
|
|
555
|
+
const vowelsInBase = stem.split("").filter(isVowel);
|
|
556
|
+
if (vowelsInBase.length > 0) {
|
|
557
|
+
const lastVowel = vowelsInBase[vowelsInBase.length - 1];
|
|
558
|
+
const widened = "a\u0131ou".includes(lastVowel) ? "a" : "e";
|
|
559
|
+
return [stem + widened];
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
518
564
|
function restoreInfinitive(stem) {
|
|
519
565
|
if (stem.length < 2)
|
|
520
566
|
return [];
|
|
@@ -538,6 +584,13 @@ function getStemCandidates(word, minStemLength = 2, maxDepth = 4) {
|
|
|
538
584
|
seen.add(apostropheStem);
|
|
539
585
|
}
|
|
540
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
|
+
}
|
|
541
594
|
let frontier = [normalized];
|
|
542
595
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
543
596
|
const nextFrontier = [];
|
|
@@ -547,9 +600,14 @@ function getStemCandidates(word, minStemLength = 2, maxDepth = 4) {
|
|
|
547
600
|
const stem = current.slice(0, -suffix.length);
|
|
548
601
|
const hardened = restoreConsonantSoftening(stem);
|
|
549
602
|
const vowelDropped = restoreVowelDrop(stem);
|
|
550
|
-
const
|
|
603
|
+
const geminated = restoreGemination(stem);
|
|
604
|
+
const isNarrowingSuffix = suffix.startsWith("yor") || suffix.includes("iyor") || suffix.includes("\u0131yor") || suffix.includes("uyor") || suffix.includes("\xFCyor");
|
|
605
|
+
const isDeYeBuffer = (stem === "di" || stem === "yi") && suffix.startsWith("y");
|
|
606
|
+
const narrowed = isNarrowingSuffix || isDeYeBuffer ? restoreVowelNarrowing(stem) : [];
|
|
607
|
+
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");
|
|
608
|
+
const verbalBases = [stem, ...hardened, ...narrowed];
|
|
551
609
|
const infinitives = verbalBases.flatMap((v) => restoreInfinitive(v));
|
|
552
|
-
const variants = [stem, ...hardened, ...vowelDropped, ...
|
|
610
|
+
const variants = [stem, ...hardened, ...vowelDropped, ...geminated, ...narrowed];
|
|
553
611
|
for (const variant of variants) {
|
|
554
612
|
if (!seen.has(variant) && variant !== normalized) {
|
|
555
613
|
seen.add(variant);
|
|
@@ -557,6 +615,14 @@ function getStemCandidates(word, minStemLength = 2, maxDepth = 4) {
|
|
|
557
615
|
candidatesWithWeight.push({ candidate: variant, baseLength: stem.length });
|
|
558
616
|
}
|
|
559
617
|
}
|
|
618
|
+
for (const inf of infinitives) {
|
|
619
|
+
if (!seen.has(inf) && inf !== normalized) {
|
|
620
|
+
seen.add(inf);
|
|
621
|
+
nextFrontier.push(inf);
|
|
622
|
+
const weight = isVerbSuffix ? stem.length + 5 : stem.length;
|
|
623
|
+
candidatesWithWeight.push({ candidate: inf, baseLength: weight });
|
|
624
|
+
}
|
|
625
|
+
}
|
|
560
626
|
}
|
|
561
627
|
}
|
|
562
628
|
}
|
|
@@ -574,6 +640,159 @@ var path = __toESM(require("path"));
|
|
|
574
640
|
var os = __toESM(require("os"));
|
|
575
641
|
var https = __toESM(require("https"));
|
|
576
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"]);
|
|
577
796
|
var TDK = class {
|
|
578
797
|
static BASE_URL = "https://sozluk.gov.tr";
|
|
579
798
|
static AUDIO_API_HOST = "api.sozluk.gov.tr";
|
|
@@ -654,6 +873,10 @@ M71DMi+y1+TRSJVClEMwvA4yL++7q9XZx5r5wBRWB4kQTKH5qyoZnDw7iiuh1lID
|
|
|
654
873
|
yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
655
874
|
-----END CERTIFICATE-----`
|
|
656
875
|
];
|
|
876
|
+
// Configuration
|
|
877
|
+
static defaultTimeoutMs = 8e3;
|
|
878
|
+
static defaultRetries = 1;
|
|
879
|
+
static maxCacheSize = 1e3;
|
|
657
880
|
// Cache Mechanism
|
|
658
881
|
static isCacheEnabled = false;
|
|
659
882
|
static wordCache = /* @__PURE__ */ new Map();
|
|
@@ -661,6 +884,19 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
661
884
|
static autocompleteCache = [];
|
|
662
885
|
static autocompleteSet = /* @__PURE__ */ new Set();
|
|
663
886
|
static stemCache = /* @__PURE__ */ new Map();
|
|
887
|
+
/**
|
|
888
|
+
* Configures global client options such as network timeout, retries, and cache size.
|
|
889
|
+
*/
|
|
890
|
+
static configure(config) {
|
|
891
|
+
if (config.timeoutMs !== void 0)
|
|
892
|
+
this.defaultTimeoutMs = Math.max(100, config.timeoutMs);
|
|
893
|
+
if (config.retries !== void 0)
|
|
894
|
+
this.defaultRetries = Math.max(0, config.retries);
|
|
895
|
+
if (config.cache !== void 0)
|
|
896
|
+
this.enableCache(config.cache);
|
|
897
|
+
if (config.maxCacheSize !== void 0)
|
|
898
|
+
this.maxCacheSize = Math.max(10, config.maxCacheSize);
|
|
899
|
+
}
|
|
664
900
|
/**
|
|
665
901
|
* Enables or disables in-memory caching for API requests.
|
|
666
902
|
*/
|
|
@@ -680,9 +916,50 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
680
916
|
this.autocompleteSet.clear();
|
|
681
917
|
this.stemCache.clear();
|
|
682
918
|
}
|
|
919
|
+
static setBoundedCache(map, key, value) {
|
|
920
|
+
if (map.size >= this.maxCacheSize) {
|
|
921
|
+
const firstKey = map.keys().next().value;
|
|
922
|
+
if (firstKey !== void 0)
|
|
923
|
+
map.delete(firstKey);
|
|
924
|
+
}
|
|
925
|
+
map.set(key, value);
|
|
926
|
+
}
|
|
683
927
|
static delay(ms) {
|
|
684
928
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
685
929
|
}
|
|
930
|
+
/**
|
|
931
|
+
* Internal helper that performs HTTP fetch with timeout and automatic retry on network/5xx errors.
|
|
932
|
+
*/
|
|
933
|
+
static async fetchWithRetry(url, options = {}, retries = this.defaultRetries, timeoutMs = this.defaultTimeoutMs) {
|
|
934
|
+
let lastError;
|
|
935
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
936
|
+
try {
|
|
937
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
938
|
+
const headers = {
|
|
939
|
+
"User-Agent": "TDK-API-Nodejs-Wrapper/1.0",
|
|
940
|
+
...options.headers || {}
|
|
941
|
+
};
|
|
942
|
+
const res = await fetch(url, { ...options, headers, signal });
|
|
943
|
+
if (res.ok || res.status >= 400 && res.status < 500) {
|
|
944
|
+
return res;
|
|
945
|
+
}
|
|
946
|
+
if (attempt < retries) {
|
|
947
|
+
await this.delay(200 * (attempt + 1));
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
return res;
|
|
951
|
+
} catch (err) {
|
|
952
|
+
lastError = err;
|
|
953
|
+
if (attempt < retries) {
|
|
954
|
+
await this.delay(200 * (attempt + 1));
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
throw new TDKNetworkError(`Request to ${url} failed after ${retries + 1} attempts.`, {
|
|
960
|
+
cause: lastError
|
|
961
|
+
});
|
|
962
|
+
}
|
|
686
963
|
/**
|
|
687
964
|
* Fetches detailed information for a given word from the TDK Dictionary.
|
|
688
965
|
*/
|
|
@@ -697,9 +974,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
697
974
|
const url = `${this.BASE_URL}/gts?ara=${encodeURIComponent(cleanWord)}`;
|
|
698
975
|
let response;
|
|
699
976
|
try {
|
|
700
|
-
response = await
|
|
701
|
-
headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" }
|
|
702
|
-
});
|
|
977
|
+
response = await this.fetchWithRetry(url);
|
|
703
978
|
} catch (error) {
|
|
704
979
|
throw new TDKNetworkError("Failed to fetch word from TDK: request failed.", { cause: error });
|
|
705
980
|
}
|
|
@@ -716,12 +991,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
716
991
|
}
|
|
717
992
|
if (!Array.isArray(data) && data && "error" in data) {
|
|
718
993
|
if (this.isCacheEnabled)
|
|
719
|
-
this.wordCache
|
|
994
|
+
this.setBoundedCache(this.wordCache, cleanWord, []);
|
|
720
995
|
return [];
|
|
721
996
|
}
|
|
722
997
|
const results = data;
|
|
723
998
|
if (this.isCacheEnabled) {
|
|
724
|
-
this.wordCache
|
|
999
|
+
this.setBoundedCache(this.wordCache, cleanWord, results);
|
|
725
1000
|
}
|
|
726
1001
|
return results;
|
|
727
1002
|
}
|
|
@@ -848,17 +1123,17 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
848
1123
|
return this.stemCache.get(clean);
|
|
849
1124
|
}
|
|
850
1125
|
if (await this.isHeadword(clean)) {
|
|
851
|
-
this.stemCache
|
|
1126
|
+
this.setBoundedCache(this.stemCache, clean, clean);
|
|
852
1127
|
return clean;
|
|
853
1128
|
}
|
|
854
1129
|
const candidates = getStemCandidates(clean);
|
|
855
1130
|
for (const candidate of candidates) {
|
|
856
1131
|
if (await this.isHeadword(candidate)) {
|
|
857
|
-
this.stemCache
|
|
1132
|
+
this.setBoundedCache(this.stemCache, clean, candidate);
|
|
858
1133
|
return candidate;
|
|
859
1134
|
}
|
|
860
1135
|
}
|
|
861
|
-
this.stemCache
|
|
1136
|
+
this.setBoundedCache(this.stemCache, clean, null);
|
|
862
1137
|
return null;
|
|
863
1138
|
}
|
|
864
1139
|
/**
|
|
@@ -1081,25 +1356,45 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1081
1356
|
* Checks spelling and returns suggestions if wrong.
|
|
1082
1357
|
*/
|
|
1083
1358
|
static async checkSpelling(word) {
|
|
1359
|
+
if (!word || word.trim() === "") {
|
|
1360
|
+
return { isCorrect: false, word };
|
|
1361
|
+
}
|
|
1362
|
+
const cleanWord = word.trim().toLocaleLowerCase("tr-TR");
|
|
1084
1363
|
const results = await this.getWord(word);
|
|
1085
1364
|
if (results.length > 0) {
|
|
1086
1365
|
return { isCorrect: true, word };
|
|
1087
1366
|
}
|
|
1367
|
+
if (COMMON_MISSPELLINGS[cleanWord]) {
|
|
1368
|
+
return { isCorrect: false, word, suggestion: COMMON_MISSPELLINGS[cleanWord] };
|
|
1369
|
+
}
|
|
1370
|
+
const seyMatch = cleanWord.match(/^(.+?)(?:şey|sey)([ıiuaeüodekmnl]+)?$/);
|
|
1371
|
+
if (seyMatch && !SEY_EXCEPTIONS.has(cleanWord)) {
|
|
1372
|
+
let prefix = seyMatch[1];
|
|
1373
|
+
const suffix = seyMatch[2] || "";
|
|
1374
|
+
if (prefix === "hicbir")
|
|
1375
|
+
prefix = "hi\xE7bir";
|
|
1376
|
+
if (prefix === "cok")
|
|
1377
|
+
prefix = "\xE7ok";
|
|
1378
|
+
return {
|
|
1379
|
+
isCorrect: false,
|
|
1380
|
+
word,
|
|
1381
|
+
suggestion: `${prefix} \u015Fey${suffix}`
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1088
1384
|
const daily = await this.getDailyContent();
|
|
1089
1385
|
if (daily) {
|
|
1090
|
-
const syydMatch = daily.syyd.find((s) => s.yanliskelime.toLocaleLowerCase("tr-TR") ===
|
|
1386
|
+
const syydMatch = daily.syyd.find((s) => s.yanliskelime.toLocaleLowerCase("tr-TR") === cleanWord);
|
|
1091
1387
|
if (syydMatch) {
|
|
1092
1388
|
return { isCorrect: false, word, suggestion: syydMatch.dogrukelime };
|
|
1093
1389
|
}
|
|
1094
|
-
const mixMatch = daily.karistirma.find((s) => s.yanlis.toLocaleLowerCase("tr-TR") ===
|
|
1390
|
+
const mixMatch = daily.karistirma.find((s) => s.yanlis.toLocaleLowerCase("tr-TR") === cleanWord);
|
|
1095
1391
|
if (mixMatch) {
|
|
1096
1392
|
return { isCorrect: false, word, suggestion: mixMatch.dogru };
|
|
1097
1393
|
}
|
|
1098
1394
|
}
|
|
1099
1395
|
const root = await this.findRoot(word);
|
|
1100
1396
|
if (root) {
|
|
1101
|
-
const
|
|
1102
|
-
const isInflected = root !== cleanWord2;
|
|
1397
|
+
const isInflected = root !== cleanWord;
|
|
1103
1398
|
return {
|
|
1104
1399
|
isCorrect: true,
|
|
1105
1400
|
word,
|
|
@@ -1110,24 +1405,32 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1110
1405
|
if (this.autocompleteCache.length === 0) {
|
|
1111
1406
|
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
1112
1407
|
}
|
|
1113
|
-
const
|
|
1408
|
+
for (const candidate of this.autocompleteCache) {
|
|
1409
|
+
if (candidate.includes(" ")) {
|
|
1410
|
+
const candidateNoSpace = candidate.replace(/\s+/g, "").toLocaleLowerCase("tr-TR");
|
|
1411
|
+
if (candidateNoSpace === cleanWord) {
|
|
1412
|
+
return { isCorrect: false, word, suggestion: candidate };
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1114
1416
|
let best = null;
|
|
1115
1417
|
for (const candidate of this.autocompleteCache) {
|
|
1116
1418
|
if (candidate.includes(" ") || candidate !== candidate.toLocaleLowerCase("tr-TR"))
|
|
1117
1419
|
continue;
|
|
1118
1420
|
if (Math.abs(candidate.length - cleanWord.length) > 2)
|
|
1119
1421
|
continue;
|
|
1120
|
-
const
|
|
1121
|
-
if (
|
|
1422
|
+
const rawDist = this.damerauLevenshtein(cleanWord, candidate);
|
|
1423
|
+
if (rawDist === 0)
|
|
1122
1424
|
continue;
|
|
1123
1425
|
const firstMismatch = candidate[0] === cleanWord[0] ? 0 : 1;
|
|
1124
1426
|
const lengthMismatch = candidate.length === cleanWord.length ? 0 : 1;
|
|
1427
|
+
const distance = rawDist + (firstMismatch > 0 ? 1.2 : 0);
|
|
1125
1428
|
const better = !best || distance < best.distance || distance === best.distance && firstMismatch < best.firstMismatch || distance === best.distance && firstMismatch === best.firstMismatch && lengthMismatch < best.lengthMismatch;
|
|
1126
1429
|
if (better) {
|
|
1127
|
-
best = { candidate, distance, firstMismatch, lengthMismatch };
|
|
1430
|
+
best = { candidate, distance, rawDist, firstMismatch, lengthMismatch };
|
|
1128
1431
|
}
|
|
1129
1432
|
}
|
|
1130
|
-
if (best && best.
|
|
1433
|
+
if (best && best.rawDist <= 2 && (best.firstMismatch === 0 || best.rawDist <= 1)) {
|
|
1131
1434
|
return { isCorrect: false, word, suggestion: best.candidate };
|
|
1132
1435
|
}
|
|
1133
1436
|
return { isCorrect: false, word };
|
|
@@ -1534,14 +1837,16 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1534
1837
|
meaningCount: meaningsA.length,
|
|
1535
1838
|
origin: originA,
|
|
1536
1839
|
syllables: this.syllabicate(a),
|
|
1537
|
-
harmony: this.checkVowelHarmony(a)
|
|
1840
|
+
harmony: this.checkVowelHarmony(a),
|
|
1841
|
+
labialHarmony: this.checkLabialHarmony(a)
|
|
1538
1842
|
},
|
|
1539
1843
|
b: {
|
|
1540
1844
|
word: b,
|
|
1541
1845
|
meaningCount: meaningsB.length,
|
|
1542
1846
|
origin: originB,
|
|
1543
1847
|
syllables: this.syllabicate(b),
|
|
1544
|
-
harmony: this.checkVowelHarmony(b)
|
|
1848
|
+
harmony: this.checkVowelHarmony(b),
|
|
1849
|
+
labialHarmony: this.checkLabialHarmony(b)
|
|
1545
1850
|
}
|
|
1546
1851
|
};
|
|
1547
1852
|
}
|
|
@@ -1672,9 +1977,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1672
1977
|
}
|
|
1673
1978
|
/**
|
|
1674
1979
|
* Syllabicates a Turkish word based on general grammar rules.
|
|
1980
|
+
* Handles syllable separation for vowels, single consonants, double consonants,
|
|
1981
|
+
* and western loanword three-consonant clusters (e.g. e-lek-trik, kon-trol, or-kes-tra).
|
|
1675
1982
|
*/
|
|
1676
1983
|
static syllabicate(word) {
|
|
1677
1984
|
const vowels = /[aeıioöuüAEIİOÖUÜ]/;
|
|
1985
|
+
const ONSET_CLUSTERS = /* @__PURE__ */ new Set(["tr", "pr", "kr", "gr", "br", "fr", "dr", "pl", "kl", "fl", "bl", "gl"]);
|
|
1678
1986
|
const result = [];
|
|
1679
1987
|
let currentSyllable = "";
|
|
1680
1988
|
for (let i = word.length - 1; i >= 0; i--) {
|
|
@@ -1685,8 +1993,13 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1685
1993
|
currentSyllable = word[i - 1] + currentSyllable;
|
|
1686
1994
|
i--;
|
|
1687
1995
|
} else if (i - 2 >= 0 && !vowels.test(word[i - 2])) {
|
|
1688
|
-
|
|
1689
|
-
|
|
1996
|
+
if (i - 3 >= 0 && !vowels.test(word[i - 3]) && ONSET_CLUSTERS.has((word[i - 2] + word[i - 1]).toLowerCase())) {
|
|
1997
|
+
currentSyllable = word[i - 2] + word[i - 1] + currentSyllable;
|
|
1998
|
+
i -= 2;
|
|
1999
|
+
} else {
|
|
2000
|
+
currentSyllable = word[i - 1] + currentSyllable;
|
|
2001
|
+
i--;
|
|
2002
|
+
}
|
|
1690
2003
|
}
|
|
1691
2004
|
}
|
|
1692
2005
|
result.unshift(currentSyllable);
|
|
@@ -1716,10 +2029,408 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1716
2029
|
const hasFront = frontVowels.test(lower);
|
|
1717
2030
|
return !(hasBack && hasFront);
|
|
1718
2031
|
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Checks if a word follows Turkish Minor Vowel Harmony (Küçük Ünlü Uyumu / Labial Harmony).
|
|
2034
|
+
* Rules:
|
|
2035
|
+
* 1. After an unrounded vowel (a, e, ı, i), only unrounded vowels (a, e, ı, i) can follow.
|
|
2036
|
+
* 2. After a rounded vowel (o, ö, u, ü), either an unrounded wide (a, e) or rounded narrow (u, ü) vowel can follow.
|
|
2037
|
+
* Single-syllable words and words with <=1 vowel are considered compliant by convention.
|
|
2038
|
+
*/
|
|
2039
|
+
static checkLabialHarmony(word) {
|
|
2040
|
+
const lower = word.toLocaleLowerCase("tr-TR");
|
|
2041
|
+
const vowels = lower.split("").filter((ch) => "ae\u0131io\xF6u\xFC".includes(ch));
|
|
2042
|
+
if (vowels.length <= 1)
|
|
2043
|
+
return true;
|
|
2044
|
+
for (let i = 0; i < vowels.length - 1; i++) {
|
|
2045
|
+
const v1 = vowels[i];
|
|
2046
|
+
const v2 = vowels[i + 1];
|
|
2047
|
+
if ("ae\u0131i".includes(v1)) {
|
|
2048
|
+
if (!"ae\u0131i".includes(v2))
|
|
2049
|
+
return false;
|
|
2050
|
+
} else if ("o\xF6u\xFC".includes(v1)) {
|
|
2051
|
+
if (!"aeu\xFC".includes(v2))
|
|
2052
|
+
return false;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
return true;
|
|
2056
|
+
}
|
|
2057
|
+
/**
|
|
2058
|
+
* Searches TDK headwords using a wildcard / pattern string.
|
|
2059
|
+
* Wildcards:
|
|
2060
|
+
* '_' or '?' matches any single character
|
|
2061
|
+
* '*' matches zero or more characters
|
|
2062
|
+
* Example: "k_l_m" matches "kalem", "kelam", "kilim".
|
|
2063
|
+
* Runs in-memory against TDK's 81k headword list.
|
|
2064
|
+
*/
|
|
2065
|
+
static async patternSearch(pattern, options) {
|
|
2066
|
+
if (!pattern || pattern.trim() === "")
|
|
2067
|
+
return [];
|
|
2068
|
+
await this.ensureAutocompleteLoaded();
|
|
2069
|
+
const cleanPattern = pattern.trim().toLocaleLowerCase("tr-TR");
|
|
2070
|
+
const escaped = cleanPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/[_?]/g, "[\\p{L}]").replace(/\*/g, "[\\p{L}]*");
|
|
2071
|
+
const regex = new RegExp(`^${escaped}$`, "u");
|
|
2072
|
+
const max = options?.maxResults ?? 50;
|
|
2073
|
+
const matches = [];
|
|
2074
|
+
for (const headword of this.autocompleteCache) {
|
|
2075
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
2076
|
+
if (regex.test(lower)) {
|
|
2077
|
+
matches.push(headword);
|
|
2078
|
+
if (matches.length >= max)
|
|
2079
|
+
break;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
return matches;
|
|
2083
|
+
}
|
|
2084
|
+
/**
|
|
2085
|
+
* Finds headwords in TDK that can be formed from the given letters (anagrams).
|
|
2086
|
+
* If exact-length anagrams exist, they are returned.
|
|
2087
|
+
* If none exist (or exactLength is false), valid sub-anagrams (words using a subset of the letters,
|
|
2088
|
+
* minimum 3 letters) are returned, sorted by length descending.
|
|
2089
|
+
*/
|
|
2090
|
+
static async findAnagrams(letters, options) {
|
|
2091
|
+
if (!letters || letters.trim() === "")
|
|
2092
|
+
return [];
|
|
2093
|
+
await this.ensureAutocompleteLoaded();
|
|
2094
|
+
const clean = letters.trim().toLocaleLowerCase("tr-TR").replace(/[^a-zçğıöşüâîû]/gi, "");
|
|
2095
|
+
if (clean.length === 0)
|
|
2096
|
+
return [];
|
|
2097
|
+
const forceExact = options?.exactLength === true;
|
|
2098
|
+
const max = options?.maxResults ?? 50;
|
|
2099
|
+
const getFrequency = (str) => {
|
|
2100
|
+
const freq = {};
|
|
2101
|
+
for (const ch of str) {
|
|
2102
|
+
freq[ch] = (freq[ch] || 0) + 1;
|
|
2103
|
+
}
|
|
2104
|
+
return freq;
|
|
2105
|
+
};
|
|
2106
|
+
const targetFreq = getFrequency(clean);
|
|
2107
|
+
const exactMatches = [];
|
|
2108
|
+
const subMatches = [];
|
|
2109
|
+
for (const headword of this.autocompleteCache) {
|
|
2110
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
2111
|
+
if (lower.includes(" ") || lower.includes("-"))
|
|
2112
|
+
continue;
|
|
2113
|
+
if (lower.length > clean.length || lower.length < 3)
|
|
2114
|
+
continue;
|
|
2115
|
+
const wordFreq = getFrequency(lower);
|
|
2116
|
+
let isValid = true;
|
|
2117
|
+
for (const [ch, count] of Object.entries(wordFreq)) {
|
|
2118
|
+
if (!targetFreq[ch] || targetFreq[ch] < count) {
|
|
2119
|
+
isValid = false;
|
|
2120
|
+
break;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
if (isValid && lower !== clean) {
|
|
2124
|
+
if (lower.length === clean.length) {
|
|
2125
|
+
exactMatches.push(headword);
|
|
2126
|
+
} else {
|
|
2127
|
+
subMatches.push(headword);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
if (exactMatches.length > 0 || forceExact) {
|
|
2132
|
+
return exactMatches.slice(0, max);
|
|
2133
|
+
}
|
|
2134
|
+
subMatches.sort((a, b) => b.length - a.length || a.localeCompare(b, "tr-TR"));
|
|
2135
|
+
return subMatches.slice(0, max);
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Finds words in TDK that rhyme with the given word (sharing the same ending suffix/letters).
|
|
2139
|
+
* @param word The target word
|
|
2140
|
+
* @param options.minLetters Minimum number of ending characters that must match (default: 3)
|
|
2141
|
+
* @param options.maxResults Maximum number of rhyme results to return (default: 50)
|
|
2142
|
+
*/
|
|
2143
|
+
static async findRhymes(word, options) {
|
|
2144
|
+
if (!word || word.trim() === "")
|
|
2145
|
+
return [];
|
|
2146
|
+
await this.ensureAutocompleteLoaded();
|
|
2147
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR");
|
|
2148
|
+
const minLetters = Math.min(options?.minLetters ?? 3, clean.length);
|
|
2149
|
+
const max = options?.maxResults ?? 50;
|
|
2150
|
+
const suffix = clean.slice(-minLetters);
|
|
2151
|
+
const results = [];
|
|
2152
|
+
for (const headword of this.autocompleteCache) {
|
|
2153
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
2154
|
+
if (lower !== clean && lower.endsWith(suffix) && !lower.includes(" ")) {
|
|
2155
|
+
results.push(headword);
|
|
2156
|
+
if (results.length >= max)
|
|
2157
|
+
break;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
return results;
|
|
2161
|
+
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Performs comprehensive spelling, grammar, and syntax proofreading on a Turkish text.
|
|
2164
|
+
* Detects:
|
|
2165
|
+
* 1. Conjunction 'da/de' erroneously joined to verbs or words (e.g. "gitsende" -> "gitsen de")
|
|
2166
|
+
* 2. Conjunction 'ki' erroneously joined to verbs (e.g. "gördümki" -> "gördüm ki"), respecting SOMBAHÇEMİ exceptions
|
|
2167
|
+
* 3. Question particle 'mi/mı/mu/mü' erroneously joined to words (e.g. "geldimi" -> "geldi mi")
|
|
2168
|
+
* 4. Misspelled words with dictionary suggestions (via edit-distance & morphology)
|
|
2169
|
+
*/
|
|
2170
|
+
static async proofread(text) {
|
|
2171
|
+
if (!text || text.trim() === "") {
|
|
2172
|
+
return { text: text || "", issues: [], isCorrect: true };
|
|
2173
|
+
}
|
|
2174
|
+
await this.ensureAutocompleteLoaded();
|
|
2175
|
+
const issues = [];
|
|
2176
|
+
const SOMBAHCEMI = /* @__PURE__ */ new Set([
|
|
2177
|
+
"sanki",
|
|
2178
|
+
"oysaki",
|
|
2179
|
+
"mademki",
|
|
2180
|
+
"belki",
|
|
2181
|
+
"halbuki",
|
|
2182
|
+
"\xE7\xFCnk\xFC",
|
|
2183
|
+
"me\u011Ferki",
|
|
2184
|
+
"illaki"
|
|
2185
|
+
]);
|
|
2186
|
+
const PHRASE_MISTAKES = [
|
|
2187
|
+
{
|
|
2188
|
+
regex: /\bhiç\s+bir\b/gi,
|
|
2189
|
+
suggestion: "hi\xE7bir",
|
|
2190
|
+
message: "'hi\xE7bir' belgisiz s\u0131fat\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2191
|
+
type: "spelling"
|
|
2192
|
+
},
|
|
2193
|
+
{
|
|
2194
|
+
regex: /\bbir\s+çok\b/gi,
|
|
2195
|
+
suggestion: "bir\xE7ok",
|
|
2196
|
+
message: "'bir\xE7ok' belgisiz s\u0131fat\u0131/zamiri biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2197
|
+
type: "spelling"
|
|
2198
|
+
},
|
|
2199
|
+
{
|
|
2200
|
+
regex: /\bbir\s+kaç\b/gi,
|
|
2201
|
+
suggestion: "birka\xE7",
|
|
2202
|
+
message: "'birka\xE7' belgisiz s\u0131fat\u0131/zamiri biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2203
|
+
type: "spelling"
|
|
2204
|
+
},
|
|
2205
|
+
{
|
|
2206
|
+
regex: /\bbir\s+az\b/gi,
|
|
2207
|
+
suggestion: "biraz",
|
|
2208
|
+
message: "'biraz' s\xF6zc\xFC\u011F\xFC biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2209
|
+
type: "spelling"
|
|
2210
|
+
},
|
|
2211
|
+
{
|
|
2212
|
+
regex: /\bher\s+hangi\b/gi,
|
|
2213
|
+
suggestion: "herhangi",
|
|
2214
|
+
message: "'herhangi' s\xF6zc\xFC\u011F\xFC biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2215
|
+
type: "spelling"
|
|
2216
|
+
},
|
|
2217
|
+
{
|
|
2218
|
+
regex: /\bgit\s+gide\b/gi,
|
|
2219
|
+
suggestion: "gitgide",
|
|
2220
|
+
message: "'gitgide' zarf\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2221
|
+
type: "spelling"
|
|
2222
|
+
},
|
|
2223
|
+
{
|
|
2224
|
+
regex: /\bbirden\s+bire\b/gi,
|
|
2225
|
+
suggestion: "birdenbire",
|
|
2226
|
+
message: "'birdenbire' zarf\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2227
|
+
type: "spelling"
|
|
2228
|
+
},
|
|
2229
|
+
{
|
|
2230
|
+
regex: /\brast\s+gele\b/gi,
|
|
2231
|
+
suggestion: "rastgele",
|
|
2232
|
+
message: "'rastgele' zarf\u0131 biti\u015Fik yaz\u0131lmal\u0131d\u0131r.",
|
|
2233
|
+
type: "spelling"
|
|
2234
|
+
}
|
|
2235
|
+
];
|
|
2236
|
+
const coveredRanges = [];
|
|
2237
|
+
for (const pm of PHRASE_MISTAKES) {
|
|
2238
|
+
let pmMatch;
|
|
2239
|
+
while ((pmMatch = pm.regex.exec(text)) !== null) {
|
|
2240
|
+
const start = pmMatch.index;
|
|
2241
|
+
const end = start + pmMatch[0].length;
|
|
2242
|
+
coveredRanges.push({ start, end });
|
|
2243
|
+
issues.push({
|
|
2244
|
+
type: pm.type,
|
|
2245
|
+
word: pmMatch[0],
|
|
2246
|
+
startIndex: start,
|
|
2247
|
+
endIndex: end,
|
|
2248
|
+
suggestion: pm.suggestion,
|
|
2249
|
+
message: pm.message
|
|
2250
|
+
});
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
const tokenRegex = /[\p{L}0-9'’]+/gu;
|
|
2254
|
+
let match;
|
|
2255
|
+
while ((match = tokenRegex.exec(text)) !== null) {
|
|
2256
|
+
const rawWord = match[0];
|
|
2257
|
+
const startIndex = match.index;
|
|
2258
|
+
const endIndex = startIndex + rawWord.length;
|
|
2259
|
+
const lower = rawWord.toLocaleLowerCase("tr-TR");
|
|
2260
|
+
if (/^\d+$/.test(lower))
|
|
2261
|
+
continue;
|
|
2262
|
+
if (coveredRanges.some((r) => startIndex >= r.start && endIndex <= r.end))
|
|
2263
|
+
continue;
|
|
2264
|
+
let flagged = false;
|
|
2265
|
+
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)?)$/);
|
|
2266
|
+
if (questionMatch) {
|
|
2267
|
+
const base = questionMatch[1];
|
|
2268
|
+
const particle = questionMatch[2];
|
|
2269
|
+
if (base.length >= 2 && (await this.isHeadword(base) || await this.findRoot(base) !== null)) {
|
|
2270
|
+
if (!await this.isHeadword(lower)) {
|
|
2271
|
+
issues.push({
|
|
2272
|
+
type: "question_particle",
|
|
2273
|
+
word: rawWord,
|
|
2274
|
+
startIndex,
|
|
2275
|
+
endIndex,
|
|
2276
|
+
suggestion: `${base} ${particle}`,
|
|
2277
|
+
message: `'${particle}' soru eki kendinden \xF6nceki kelimeden ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
|
|
2278
|
+
});
|
|
2279
|
+
flagged = true;
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
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;
|
|
2284
|
+
if (!flagged && lower.endsWith("ki") && lower.length > 3) {
|
|
2285
|
+
const base = lower.slice(0, -2);
|
|
2286
|
+
if (!SOMBAHCEMI.has(lower)) {
|
|
2287
|
+
if (!await this.isHeadword(lower)) {
|
|
2288
|
+
const root = await this.findRoot(base);
|
|
2289
|
+
const isVerb = (base === "demek" || base === "kald\u0131" || base === "yeter" || base === "bilmem" || VERB_CONJUGATION_REGEX.test(base)) && (root ? root.endsWith("mek") || root.endsWith("mak") : true);
|
|
2290
|
+
if (isVerb) {
|
|
2291
|
+
issues.push({
|
|
2292
|
+
type: "conjunction_ki",
|
|
2293
|
+
word: rawWord,
|
|
2294
|
+
startIndex,
|
|
2295
|
+
endIndex,
|
|
2296
|
+
suggestion: `${base} ki`,
|
|
2297
|
+
message: `'ki' ba\u011Flac\u0131 ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
|
|
2298
|
+
});
|
|
2299
|
+
flagged = true;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
if (!flagged && (lower.endsWith("de") || lower.endsWith("da") || lower.endsWith("te") || lower.endsWith("ta")) && lower.length > 3) {
|
|
2305
|
+
const base = lower.slice(0, -2);
|
|
2306
|
+
const ending = lower.slice(-2);
|
|
2307
|
+
if (!await this.isHeadword(lower)) {
|
|
2308
|
+
const root = await this.findRoot(base);
|
|
2309
|
+
const isVerb = VERB_CONJUGATION_REGEX.test(base) && (root ? root.endsWith("mek") || root.endsWith("mak") : false);
|
|
2310
|
+
if (isVerb) {
|
|
2311
|
+
const correctEnding = ending.startsWith("t") ? ending === "te" ? "de" : "da" : ending;
|
|
2312
|
+
issues.push({
|
|
2313
|
+
type: "conjunction_da",
|
|
2314
|
+
word: rawWord,
|
|
2315
|
+
startIndex,
|
|
2316
|
+
endIndex,
|
|
2317
|
+
suggestion: `${base} ${correctEnding}`,
|
|
2318
|
+
message: `'da/de' ba\u011Flac\u0131 fiillerden sonra her zaman ayr\u0131 yaz\u0131l\u0131r (ba\u011Fla\xE7 olan da/de sertle\u015Fmez).`
|
|
2319
|
+
});
|
|
2320
|
+
flagged = true;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
const seyMatch = lower.match(/^(.+?)(?:şey|sey)([ıiuaeüodekmnl]+)?$/);
|
|
2325
|
+
if (!flagged && seyMatch && !SEY_EXCEPTIONS.has(lower)) {
|
|
2326
|
+
let prefix = seyMatch[1];
|
|
2327
|
+
const suffix = seyMatch[2] || "";
|
|
2328
|
+
if (prefix === "hicbir")
|
|
2329
|
+
prefix = "hi\xE7bir";
|
|
2330
|
+
if (prefix === "cok")
|
|
2331
|
+
prefix = "\xE7ok";
|
|
2332
|
+
issues.push({
|
|
2333
|
+
type: "spelling",
|
|
2334
|
+
word: rawWord,
|
|
2335
|
+
startIndex,
|
|
2336
|
+
endIndex,
|
|
2337
|
+
suggestion: `${prefix} \u015Fey${suffix}`,
|
|
2338
|
+
message: "'\u015Fey' s\xF6zc\xFC\u011F\xFC kendinden \xF6nceki kelimeden ayr\u0131 yaz\u0131lmal\u0131d\u0131r."
|
|
2339
|
+
});
|
|
2340
|
+
flagged = true;
|
|
2341
|
+
}
|
|
2342
|
+
if (!flagged && lower === "yada") {
|
|
2343
|
+
issues.push({
|
|
2344
|
+
type: "spelling",
|
|
2345
|
+
word: rawWord,
|
|
2346
|
+
startIndex,
|
|
2347
|
+
endIndex,
|
|
2348
|
+
suggestion: "ya da",
|
|
2349
|
+
message: "'ya da' ba\u011Flac\u0131 her zaman ayr\u0131 yaz\u0131l\u0131r."
|
|
2350
|
+
});
|
|
2351
|
+
flagged = true;
|
|
2352
|
+
}
|
|
2353
|
+
if (!flagged && (lower === "burda" || lower === "\u015Furda" || lower === "surda" || lower === "orda" || lower === "i\xE7erde" || lower === "icerde" || lower === "d\u0131\u015Farda" || lower === "disarda" || lower === "yukarda")) {
|
|
2354
|
+
const correct = COMMON_MISSPELLINGS[lower] || lower;
|
|
2355
|
+
issues.push({
|
|
2356
|
+
type: "spelling",
|
|
2357
|
+
word: rawWord,
|
|
2358
|
+
startIndex,
|
|
2359
|
+
endIndex,
|
|
2360
|
+
suggestion: correct,
|
|
2361
|
+
message: `'${rawWord}' s\xF6zc\xFC\u011F\xFCnde \xFCnl\xFC d\xFC\u015Fmesi yap\u0131lmaz.`
|
|
2362
|
+
});
|
|
2363
|
+
flagged = true;
|
|
2364
|
+
}
|
|
2365
|
+
if (!flagged) {
|
|
2366
|
+
const check = await this.checkSpelling(rawWord);
|
|
2367
|
+
if (!check.isCorrect) {
|
|
2368
|
+
issues.push({
|
|
2369
|
+
type: "spelling",
|
|
2370
|
+
word: rawWord,
|
|
2371
|
+
startIndex,
|
|
2372
|
+
endIndex,
|
|
2373
|
+
suggestion: check.suggestion,
|
|
2374
|
+
message: check.suggestion ? `'${rawWord}' yanl\u0131\u015F yaz\u0131lm\u0131\u015F olabilir.` : `'${rawWord}' s\xF6zl\xFCkte bulunamad\u0131.`
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
issues.sort((a, b) => a.startIndex - b.startIndex);
|
|
2380
|
+
return {
|
|
2381
|
+
text,
|
|
2382
|
+
issues,
|
|
2383
|
+
isCorrect: issues.length === 0
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
};
|
|
2387
|
+
var TDKClient = class {
|
|
2388
|
+
constructor(config) {
|
|
2389
|
+
if (config) {
|
|
2390
|
+
TDK.configure(config);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
getWord(word) {
|
|
2394
|
+
return TDK.getWord(word);
|
|
2395
|
+
}
|
|
2396
|
+
getMeanings(word) {
|
|
2397
|
+
return TDK.getMeanings(word);
|
|
2398
|
+
}
|
|
2399
|
+
checkSpelling(word) {
|
|
2400
|
+
return TDK.checkSpelling(word);
|
|
2401
|
+
}
|
|
2402
|
+
findRoot(word) {
|
|
2403
|
+
return TDK.findRoot(word);
|
|
2404
|
+
}
|
|
2405
|
+
stem(word) {
|
|
2406
|
+
return TDK.stem(word);
|
|
2407
|
+
}
|
|
2408
|
+
proofread(text) {
|
|
2409
|
+
return TDK.proofread(text);
|
|
2410
|
+
}
|
|
2411
|
+
patternSearch(pattern, options) {
|
|
2412
|
+
return TDK.patternSearch(pattern, options);
|
|
2413
|
+
}
|
|
2414
|
+
findAnagrams(letters, options) {
|
|
2415
|
+
return TDK.findAnagrams(letters, options);
|
|
2416
|
+
}
|
|
2417
|
+
findRhymes(word, options) {
|
|
2418
|
+
return TDK.findRhymes(word, options);
|
|
2419
|
+
}
|
|
2420
|
+
syllabicate(word) {
|
|
2421
|
+
return TDK.syllabicate(word);
|
|
2422
|
+
}
|
|
2423
|
+
checkVowelHarmony(word) {
|
|
2424
|
+
return TDK.checkVowelHarmony(word);
|
|
2425
|
+
}
|
|
2426
|
+
checkLabialHarmony(word) {
|
|
2427
|
+
return TDK.checkLabialHarmony(word);
|
|
2428
|
+
}
|
|
1719
2429
|
};
|
|
1720
2430
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1721
2431
|
0 && (module.exports = {
|
|
1722
2432
|
TDK,
|
|
2433
|
+
TDKClient,
|
|
1723
2434
|
TDKError,
|
|
1724
2435
|
TDKNetworkError,
|
|
1725
2436
|
TDKValidationError,
|
|
@@ -1728,6 +2439,8 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1728
2439
|
getStemCandidates,
|
|
1729
2440
|
isVowel,
|
|
1730
2441
|
restoreConsonantSoftening,
|
|
2442
|
+
restoreGemination,
|
|
1731
2443
|
restoreInfinitive,
|
|
1732
|
-
restoreVowelDrop
|
|
2444
|
+
restoreVowelDrop,
|
|
2445
|
+
restoreVowelNarrowing
|
|
1733
2446
|
});
|