tdk-api-wrapper 1.5.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/dist/{chunk-7KJHYRJZ.mjs → chunk-NEC5MIQM.mjs} +311 -12
- package/dist/cli.js +316 -13
- package/dist/cli.mjs +6 -2
- package/dist/index.js +311 -12
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/cli.ts +6 -1
- package/src/morphology.ts +9 -0
- package/src/tdk.ts +344 -37
- package/test/proofread.test.js +28 -0
package/src/tdk.ts
CHANGED
|
@@ -25,6 +25,168 @@ import * as os from "node:os";
|
|
|
25
25
|
import * as https from "node:https";
|
|
26
26
|
import * as tls from "node:tls";
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Known frequent Turkish misspellings, erroneously joined compound words,
|
|
30
|
+
* and words where vowel dropping is prohibited by TDK (Yazım Kılavuzu).
|
|
31
|
+
*/
|
|
32
|
+
export const COMMON_MISSPELLINGS: Record<string, string> = {
|
|
33
|
+
// -şey ile biten ve ayrı yazılması zorunlu söz öbekleri
|
|
34
|
+
herşey: "her şey",
|
|
35
|
+
hersey: "her şey",
|
|
36
|
+
birşey: "bir şey",
|
|
37
|
+
birsey: "bir şey",
|
|
38
|
+
hiçbirşey: "hiçbir şey",
|
|
39
|
+
hicbirsey: "hiçbir şey",
|
|
40
|
+
çokşey: "çok şey",
|
|
41
|
+
coksey: "çok şey",
|
|
42
|
+
şeyler: "şeyler",
|
|
43
|
+
seyler: "şeyler",
|
|
44
|
+
herhangibirşey: "herhangi bir şey",
|
|
45
|
+
herhangibirsey: "herhangi bir şey",
|
|
46
|
+
|
|
47
|
+
// Sıkça birleşik yazılan ama ayrı yazılması gereken sözler
|
|
48
|
+
hergün: "her gün",
|
|
49
|
+
hergun: "her gün",
|
|
50
|
+
herzaman: "her zaman",
|
|
51
|
+
heran: "her an",
|
|
52
|
+
heryer: "her yer",
|
|
53
|
+
herbiri: "her biri",
|
|
54
|
+
pekçok: "pek çok",
|
|
55
|
+
pekcok: "pek çok",
|
|
56
|
+
pekaz: "pek az",
|
|
57
|
+
yada: "ya da",
|
|
58
|
+
tabiki: "tabii ki",
|
|
59
|
+
tabiiki: "tabii ki",
|
|
60
|
+
sağol: "sağ ol",
|
|
61
|
+
sagol: "sağ ol",
|
|
62
|
+
sağolun: "sağ olun",
|
|
63
|
+
sagolun: "sağ olun",
|
|
64
|
+
hoşçakal: "hoşça kal",
|
|
65
|
+
hoscakal: "hoşça kal",
|
|
66
|
+
hoşgeldin: "hoş geldin",
|
|
67
|
+
hosgeldin: "hoş geldin",
|
|
68
|
+
hoşgeldiniz: "hoş geldiniz",
|
|
69
|
+
hosgeldiniz: "hoş geldiniz",
|
|
70
|
+
hoşbulduk: "hoş bulduk",
|
|
71
|
+
hosbulduk: "hoş bulduk",
|
|
72
|
+
yanısıra: "yanı sıra",
|
|
73
|
+
yanisira: "yanı sıra",
|
|
74
|
+
peşisıra: "peşi sıra",
|
|
75
|
+
pesisira: "peşi sıra",
|
|
76
|
+
ardısıra: "ardı sıra",
|
|
77
|
+
ardisira: "ardı sıra",
|
|
78
|
+
artarda: "art arda",
|
|
79
|
+
yüzyüze: "yüz yüze",
|
|
80
|
+
yuzyuze: "yüz yüze",
|
|
81
|
+
elele: "el ele",
|
|
82
|
+
gözgöze: "göz göze",
|
|
83
|
+
başbaşa: "baş başa",
|
|
84
|
+
basbasa: "baş başa",
|
|
85
|
+
yanyana: "yan yana",
|
|
86
|
+
içiçe: "iç içe",
|
|
87
|
+
icice: "iç içe",
|
|
88
|
+
üstüste: "üst üste",
|
|
89
|
+
ustuste: "üst üste",
|
|
90
|
+
altalta: "alt alta",
|
|
91
|
+
önsöz: "ön söz",
|
|
92
|
+
onsoz: "ön söz",
|
|
93
|
+
önyargı: "ön yargı",
|
|
94
|
+
onyargi: "ön yargı",
|
|
95
|
+
farketmek: "fark etmek",
|
|
96
|
+
farketti: "fark etti",
|
|
97
|
+
farkettim: "fark ettim",
|
|
98
|
+
farkeder: "fark eder",
|
|
99
|
+
farketmez: "fark etmez",
|
|
100
|
+
terketmek: "terk etmek",
|
|
101
|
+
terketti: "terk etti",
|
|
102
|
+
ayırdetmek: "ayırt etmek",
|
|
103
|
+
ayırtetmek: "ayırt etmek",
|
|
104
|
+
arzetmek: "arz etmek",
|
|
105
|
+
arzederim: "arz ederim",
|
|
106
|
+
varolmak: "var olmak",
|
|
107
|
+
yokolmak: "yok olmak",
|
|
108
|
+
haketmek: "hak etmek",
|
|
109
|
+
haketti: "hak etti",
|
|
110
|
+
hakkaten: "hakikaten",
|
|
111
|
+
hiçkimse: "hiç kimse",
|
|
112
|
+
hickimse: "hiç kimse",
|
|
113
|
+
|
|
114
|
+
// Ünlü düşmesi yapılmaması gereken yer bildiren sözler (TDK Kural 15)
|
|
115
|
+
burda: "burada",
|
|
116
|
+
burdan: "buradan",
|
|
117
|
+
şurda: "şurada",
|
|
118
|
+
surda: "şurada",
|
|
119
|
+
şurdan: "şuradan",
|
|
120
|
+
surdan: "şuradan",
|
|
121
|
+
orda: "orada",
|
|
122
|
+
ordan: "oradan",
|
|
123
|
+
içerde: "içeride",
|
|
124
|
+
icerde: "içeride",
|
|
125
|
+
içerden: "içeriden",
|
|
126
|
+
icerden: "içeriden",
|
|
127
|
+
dışarda: "dışarıda",
|
|
128
|
+
disarda: "dışarıda",
|
|
129
|
+
dışardan: "dışarıdan",
|
|
130
|
+
disardan: "dışarıdan",
|
|
131
|
+
yukarda: "yukarıda",
|
|
132
|
+
yukardan: "yukarıdan",
|
|
133
|
+
|
|
134
|
+
// Sıkça yanlış yazılan sözcükler
|
|
135
|
+
herkez: "herkes",
|
|
136
|
+
yanlız: "yalnız",
|
|
137
|
+
yalnış: "yanlış",
|
|
138
|
+
orjinal: "orijinal",
|
|
139
|
+
labaratuar: "laboratuvar",
|
|
140
|
+
laboratuar: "laboratuvar",
|
|
141
|
+
şöför: "şoför",
|
|
142
|
+
sofor: "şoför",
|
|
143
|
+
egzos: "egzoz",
|
|
144
|
+
eksoz: "egzoz",
|
|
145
|
+
ekzoz: "egzoz",
|
|
146
|
+
kiprik: "kirpik",
|
|
147
|
+
kirbit: "kibrit",
|
|
148
|
+
klavuz: "kılavuz",
|
|
149
|
+
kıravat: "kravat",
|
|
150
|
+
süpriz: "sürpriz",
|
|
151
|
+
supriz: "sürpriz",
|
|
152
|
+
raslantı: "rastlantı",
|
|
153
|
+
hastahane: "hastane",
|
|
154
|
+
pastahane: "pastane",
|
|
155
|
+
postahane: "postane",
|
|
156
|
+
eczahane: "eczane",
|
|
157
|
+
meyva: "meyve",
|
|
158
|
+
sarmısak: "sarımsak",
|
|
159
|
+
dinazor: "dinozor",
|
|
160
|
+
pantalon: "pantolon",
|
|
161
|
+
tesbih: "tespih",
|
|
162
|
+
ahçı: "aşçı",
|
|
163
|
+
matba: "matbaa",
|
|
164
|
+
idda: "iddia",
|
|
165
|
+
iddaa: "iddia",
|
|
166
|
+
muhattap: "muhatap",
|
|
167
|
+
traş: "tıraş",
|
|
168
|
+
karnıbahar: "karnabahar",
|
|
169
|
+
kareografi: "koreografi",
|
|
170
|
+
poaça: "poğaça",
|
|
171
|
+
pohaça: "poğaça",
|
|
172
|
+
şarz: "şarj",
|
|
173
|
+
sarj: "şarj",
|
|
174
|
+
makina: "makine",
|
|
175
|
+
müsade: "müsaade",
|
|
176
|
+
entellektüel: "entelektüel",
|
|
177
|
+
inisiyatif: "inisiyatif",
|
|
178
|
+
insiyatif: "inisiyatif",
|
|
179
|
+
sezeryan: "sezaryen",
|
|
180
|
+
doküman: "doküman",
|
|
181
|
+
döküman: "doküman",
|
|
182
|
+
erozyon: "erozyon",
|
|
183
|
+
erizyon: "erozyon",
|
|
184
|
+
anane: "anneanne",
|
|
185
|
+
babaanne: "babaanne",
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const SEY_EXCEPTIONS = new Set(["düşey", "eşey", "konsey", "jersey", "şey"]);
|
|
189
|
+
|
|
28
190
|
/**
|
|
29
191
|
* TDK (Türk Dil Kurumu) API Wrapper
|
|
30
192
|
*/
|
|
@@ -630,32 +792,56 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
630
792
|
* Checks spelling and returns suggestions if wrong.
|
|
631
793
|
*/
|
|
632
794
|
public static async checkSpelling(word: string): Promise<SpellCheckResult> {
|
|
633
|
-
|
|
795
|
+
if (!word || word.trim() === "") {
|
|
796
|
+
return { isCorrect: false, word };
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const cleanWord = word.trim().toLocaleLowerCase("tr-TR");
|
|
800
|
+
|
|
801
|
+
// 1. Check if word exists in TDK dictionary
|
|
634
802
|
const results = await this.getWord(word);
|
|
635
803
|
if (results.length > 0) {
|
|
636
804
|
return { isCorrect: true, word };
|
|
637
805
|
}
|
|
638
806
|
|
|
639
|
-
// 2.
|
|
640
|
-
|
|
641
|
-
|
|
807
|
+
// 2. Common Turkish misspellings, erroneously joined compounds, and vowel drop errors
|
|
808
|
+
if (COMMON_MISSPELLINGS[cleanWord]) {
|
|
809
|
+
return { isCorrect: false, word, suggestion: COMMON_MISSPELLINGS[cleanWord] };
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// 3. Dynamic -şey / -sey attached check:
|
|
813
|
+
// In Turkish, 'şey' is an indefinite pronoun and is ALWAYS written separately from the preceding word
|
|
814
|
+
// (e.g. her şey, bir şey, hiçbir şey, çok şey, her şeyi, bir şeyler).
|
|
815
|
+
const seyMatch = cleanWord.match(/^(.+?)(?:şey|sey)([ıiuaeüodekmnl]+)?$/);
|
|
816
|
+
if (seyMatch && !SEY_EXCEPTIONS.has(cleanWord)) {
|
|
817
|
+
let prefix = seyMatch[1];
|
|
818
|
+
const suffix = seyMatch[2] || "";
|
|
819
|
+
if (prefix === "hicbir") prefix = "hiçbir";
|
|
820
|
+
if (prefix === "cok") prefix = "çok";
|
|
821
|
+
return {
|
|
822
|
+
isCorrect: false,
|
|
823
|
+
word,
|
|
824
|
+
suggestion: `${prefix} şey${suffix}`,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// 4. "Sıkça yapılan yanlışlar" from DailyContent
|
|
642
829
|
const daily = await this.getDailyContent();
|
|
643
830
|
if (daily) {
|
|
644
|
-
const syydMatch = daily.syyd.find(s => s.yanliskelime.toLocaleLowerCase("tr-TR") ===
|
|
831
|
+
const syydMatch = daily.syyd.find((s) => s.yanliskelime.toLocaleLowerCase("tr-TR") === cleanWord);
|
|
645
832
|
if (syydMatch) {
|
|
646
833
|
return { isCorrect: false, word, suggestion: syydMatch.dogrukelime };
|
|
647
834
|
}
|
|
648
|
-
const mixMatch = daily.karistirma.find(s => s.yanlis.toLocaleLowerCase("tr-TR") ===
|
|
835
|
+
const mixMatch = daily.karistirma.find((s) => s.yanlis.toLocaleLowerCase("tr-TR") === cleanWord);
|
|
649
836
|
if (mixMatch) {
|
|
650
837
|
return { isCorrect: false, word, suggestion: mixMatch.dogru };
|
|
651
838
|
}
|
|
652
839
|
}
|
|
653
840
|
|
|
654
|
-
//
|
|
655
|
-
// (e.g., "halılarımızın" -> "halı", "kitabımız" -> "kitap", "çocuğa" -> "çocuk")
|
|
841
|
+
// 5. Morphology Fallback: Check if the word is an inflected form or bare verb imperative of a known headword
|
|
842
|
+
// (e.g., "halılarımızın" -> "halı", "kitabımız" -> "kitap", "çocuğa" -> "çocuk", "söyle" -> "söylemek")
|
|
656
843
|
const root = await this.findRoot(word);
|
|
657
844
|
if (root) {
|
|
658
|
-
const cleanWord = word.trim().toLocaleLowerCase("tr-TR");
|
|
659
845
|
const isInflected = root !== cleanWord;
|
|
660
846
|
return {
|
|
661
847
|
isCorrect: true,
|
|
@@ -665,42 +851,44 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
665
851
|
};
|
|
666
852
|
}
|
|
667
853
|
|
|
668
|
-
//
|
|
669
|
-
// headword (by edit distance) across TDK's full ~81k-word list (the same
|
|
670
|
-
// data `getSuggestions()` uses). Restricted to single-token, lowercase
|
|
671
|
-
// headwords so it doesn't suggest compounds/phrases or proper nouns.
|
|
672
|
-
// Candidates whose length differs too much are skipped before running
|
|
673
|
-
// the O(n*m) distance calculation, both for speed and because a huge
|
|
674
|
-
// length gap can't be within the distance threshold anyway. Ties (same
|
|
675
|
-
// distance) prefer a matching first letter, then a matching length —
|
|
676
|
-
// typos rarely change the first letter, and this avoids picking
|
|
677
|
-
// whatever happens to sort alphabetically first. There's no word
|
|
678
|
-
// frequency data available, so a genuine tie can still land on a
|
|
679
|
-
// technically-correct but less commonly intended word.
|
|
854
|
+
// 6. Check if headwords with spaces match when space is removed (e.g. "ön yargı" for "önyargı")
|
|
680
855
|
if (this.autocompleteCache.length === 0) {
|
|
681
856
|
this.autocompleteCache = await this.fetchAutocompleteData();
|
|
682
857
|
}
|
|
683
|
-
const
|
|
684
|
-
|
|
858
|
+
for (const candidate of this.autocompleteCache) {
|
|
859
|
+
if (candidate.includes(" ")) {
|
|
860
|
+
const candidateNoSpace = candidate.replace(/\s+/g, "").toLocaleLowerCase("tr-TR");
|
|
861
|
+
if (candidateNoSpace === cleanWord) {
|
|
862
|
+
return { isCorrect: false, word, suggestion: candidate };
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// 7. No exact match or morphology root: fall back to closest headword by edit distance.
|
|
868
|
+
// Ties prefer matching first letter, and initial character mismatches are penalized
|
|
869
|
+
// so irrelevant foreign loanwords (like 'jersey') do not beat Turkish roots.
|
|
870
|
+
let best: { candidate: string; distance: number; rawDist: number; firstMismatch: number; lengthMismatch: number } | null = null;
|
|
685
871
|
for (const candidate of this.autocompleteCache) {
|
|
686
872
|
if (candidate.includes(" ") || candidate !== candidate.toLocaleLowerCase("tr-TR")) continue;
|
|
687
873
|
if (Math.abs(candidate.length - cleanWord.length) > 2) continue;
|
|
688
874
|
|
|
689
|
-
const
|
|
690
|
-
if (
|
|
875
|
+
const rawDist = this.damerauLevenshtein(cleanWord, candidate);
|
|
876
|
+
if (rawDist === 0) continue;
|
|
691
877
|
|
|
692
878
|
const firstMismatch = candidate[0] === cleanWord[0] ? 0 : 1;
|
|
693
879
|
const lengthMismatch = candidate.length === cleanWord.length ? 0 : 1;
|
|
880
|
+
const distance = rawDist + (firstMismatch > 0 ? 1.2 : 0);
|
|
881
|
+
|
|
694
882
|
const better =
|
|
695
883
|
!best ||
|
|
696
884
|
distance < best.distance ||
|
|
697
885
|
(distance === best.distance && firstMismatch < best.firstMismatch) ||
|
|
698
886
|
(distance === best.distance && firstMismatch === best.firstMismatch && lengthMismatch < best.lengthMismatch);
|
|
699
887
|
if (better) {
|
|
700
|
-
best = { candidate, distance, firstMismatch, lengthMismatch };
|
|
888
|
+
best = { candidate, distance, rawDist, firstMismatch, lengthMismatch };
|
|
701
889
|
}
|
|
702
890
|
}
|
|
703
|
-
if (best && best.
|
|
891
|
+
if (best && best.rawDist <= 2 && (best.firstMismatch === 0 || best.rawDist <= 1)) {
|
|
704
892
|
return { isCorrect: false, word, suggestion: best.candidate };
|
|
705
893
|
}
|
|
706
894
|
return { isCorrect: false, word };
|
|
@@ -1477,6 +1665,81 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1477
1665
|
"sanki", "oysaki", "mademki", "belki", "halbuki", "çünkü", "meğerki", "illaki"
|
|
1478
1666
|
]);
|
|
1479
1667
|
|
|
1668
|
+
// 1. Detect multi-word phrases that should be written as single compound words
|
|
1669
|
+
const PHRASE_MISTAKES: {
|
|
1670
|
+
regex: RegExp;
|
|
1671
|
+
suggestion: string;
|
|
1672
|
+
message: string;
|
|
1673
|
+
type: ProofreadIssue["type"];
|
|
1674
|
+
}[] = [
|
|
1675
|
+
{
|
|
1676
|
+
regex: /\bhiç\s+bir\b/gi,
|
|
1677
|
+
suggestion: "hiçbir",
|
|
1678
|
+
message: "'hiçbir' belgisiz sıfatı bitişik yazılmalıdır.",
|
|
1679
|
+
type: "spelling",
|
|
1680
|
+
},
|
|
1681
|
+
{
|
|
1682
|
+
regex: /\bbir\s+çok\b/gi,
|
|
1683
|
+
suggestion: "birçok",
|
|
1684
|
+
message: "'birçok' belgisiz sıfatı/zamiri bitişik yazılmalıdır.",
|
|
1685
|
+
type: "spelling",
|
|
1686
|
+
},
|
|
1687
|
+
{
|
|
1688
|
+
regex: /\bbir\s+kaç\b/gi,
|
|
1689
|
+
suggestion: "birkaç",
|
|
1690
|
+
message: "'birkaç' belgisiz sıfatı/zamiri bitişik yazılmalıdır.",
|
|
1691
|
+
type: "spelling",
|
|
1692
|
+
},
|
|
1693
|
+
{
|
|
1694
|
+
regex: /\bbir\s+az\b/gi,
|
|
1695
|
+
suggestion: "biraz",
|
|
1696
|
+
message: "'biraz' sözcüğü bitişik yazılmalıdır.",
|
|
1697
|
+
type: "spelling",
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
regex: /\bher\s+hangi\b/gi,
|
|
1701
|
+
suggestion: "herhangi",
|
|
1702
|
+
message: "'herhangi' sözcüğü bitişik yazılmalıdır.",
|
|
1703
|
+
type: "spelling",
|
|
1704
|
+
},
|
|
1705
|
+
{
|
|
1706
|
+
regex: /\bgit\s+gide\b/gi,
|
|
1707
|
+
suggestion: "gitgide",
|
|
1708
|
+
message: "'gitgide' zarfı bitişik yazılmalıdır.",
|
|
1709
|
+
type: "spelling",
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
regex: /\bbirden\s+bire\b/gi,
|
|
1713
|
+
suggestion: "birdenbire",
|
|
1714
|
+
message: "'birdenbire' zarfı bitişik yazılmalıdır.",
|
|
1715
|
+
type: "spelling",
|
|
1716
|
+
},
|
|
1717
|
+
{
|
|
1718
|
+
regex: /\brast\s+gele\b/gi,
|
|
1719
|
+
suggestion: "rastgele",
|
|
1720
|
+
message: "'rastgele' zarfı bitişik yazılmalıdır.",
|
|
1721
|
+
type: "spelling",
|
|
1722
|
+
},
|
|
1723
|
+
];
|
|
1724
|
+
|
|
1725
|
+
const coveredRanges: { start: number; end: number }[] = [];
|
|
1726
|
+
for (const pm of PHRASE_MISTAKES) {
|
|
1727
|
+
let pmMatch: RegExpExecArray | null;
|
|
1728
|
+
while ((pmMatch = pm.regex.exec(text)) !== null) {
|
|
1729
|
+
const start = pmMatch.index;
|
|
1730
|
+
const end = start + pmMatch[0].length;
|
|
1731
|
+
coveredRanges.push({ start, end });
|
|
1732
|
+
issues.push({
|
|
1733
|
+
type: pm.type,
|
|
1734
|
+
word: pmMatch[0],
|
|
1735
|
+
startIndex: start,
|
|
1736
|
+
endIndex: end,
|
|
1737
|
+
suggestion: pm.suggestion,
|
|
1738
|
+
message: pm.message,
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1480
1743
|
const tokenRegex = /[\p{L}0-9'’]+/gu;
|
|
1481
1744
|
let match: RegExpExecArray | null;
|
|
1482
1745
|
|
|
@@ -1487,6 +1750,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1487
1750
|
const lower = rawWord.toLocaleLowerCase("tr-TR");
|
|
1488
1751
|
|
|
1489
1752
|
if (/^\d+$/.test(lower)) continue;
|
|
1753
|
+
if (coveredRanges.some((r) => startIndex >= r.start && endIndex <= r.end)) continue;
|
|
1490
1754
|
|
|
1491
1755
|
let flagged = false;
|
|
1492
1756
|
|
|
@@ -1520,12 +1784,8 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1520
1784
|
if (!(await this.isHeadword(lower))) {
|
|
1521
1785
|
const root = await this.findRoot(base);
|
|
1522
1786
|
const isVerb =
|
|
1523
|
-
(
|
|
1524
|
-
|
|
1525
|
-
base === "kaldı" ||
|
|
1526
|
-
base === "yeter" ||
|
|
1527
|
-
base === "bilmem" ||
|
|
1528
|
-
VERB_CONJUGATION_REGEX.test(base);
|
|
1787
|
+
(base === "demek" || base === "kaldı" || base === "yeter" || base === "bilmem" || VERB_CONJUGATION_REGEX.test(base)) &&
|
|
1788
|
+
(root ? root.endsWith("mek") || root.endsWith("mak") : true);
|
|
1529
1789
|
|
|
1530
1790
|
if (isVerb) {
|
|
1531
1791
|
issues.push({
|
|
@@ -1549,8 +1809,8 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1549
1809
|
if (!(await this.isHeadword(lower))) {
|
|
1550
1810
|
const root = await this.findRoot(base);
|
|
1551
1811
|
const isVerb =
|
|
1552
|
-
|
|
1553
|
-
|
|
1812
|
+
VERB_CONJUGATION_REGEX.test(base) &&
|
|
1813
|
+
(root ? root.endsWith("mek") || root.endsWith("mak") : false);
|
|
1554
1814
|
|
|
1555
1815
|
if (isVerb) {
|
|
1556
1816
|
const correctEnding = ending.startsWith("t") ? (ending === "te" ? "de" : "da") : ending;
|
|
@@ -1567,7 +1827,52 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1567
1827
|
}
|
|
1568
1828
|
}
|
|
1569
1829
|
|
|
1570
|
-
// 4.
|
|
1830
|
+
// 4. Check -şey / -sey erroneously attached to preceding word
|
|
1831
|
+
const seyMatch = lower.match(/^(.+?)(?:şey|sey)([ıiuaeüodekmnl]+)?$/);
|
|
1832
|
+
if (!flagged && seyMatch && !SEY_EXCEPTIONS.has(lower)) {
|
|
1833
|
+
let prefix = seyMatch[1];
|
|
1834
|
+
const suffix = seyMatch[2] || "";
|
|
1835
|
+
if (prefix === "hicbir") prefix = "hiçbir";
|
|
1836
|
+
if (prefix === "cok") prefix = "çok";
|
|
1837
|
+
issues.push({
|
|
1838
|
+
type: "spelling",
|
|
1839
|
+
word: rawWord,
|
|
1840
|
+
startIndex,
|
|
1841
|
+
endIndex,
|
|
1842
|
+
suggestion: `${prefix} şey${suffix}`,
|
|
1843
|
+
message: "'şey' sözcüğü kendinden önceki kelimeden ayrı yazılmalıdır.",
|
|
1844
|
+
});
|
|
1845
|
+
flagged = true;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// 5. Check 'yada' conjunction mistake
|
|
1849
|
+
if (!flagged && lower === "yada") {
|
|
1850
|
+
issues.push({
|
|
1851
|
+
type: "spelling",
|
|
1852
|
+
word: rawWord,
|
|
1853
|
+
startIndex,
|
|
1854
|
+
endIndex,
|
|
1855
|
+
suggestion: "ya da",
|
|
1856
|
+
message: "'ya da' bağlacı her zaman ayrı yazılır.",
|
|
1857
|
+
});
|
|
1858
|
+
flagged = true;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
// 6. Check common vowel drop mistakes: burda, şurda, orda, vb. (TDK Kural 15)
|
|
1862
|
+
if (!flagged && (lower === "burda" || lower === "şurda" || lower === "surda" || lower === "orda" || lower === "içerde" || lower === "icerde" || lower === "dışarda" || lower === "disarda" || lower === "yukarda")) {
|
|
1863
|
+
const correct = COMMON_MISSPELLINGS[lower] || lower;
|
|
1864
|
+
issues.push({
|
|
1865
|
+
type: "spelling",
|
|
1866
|
+
word: rawWord,
|
|
1867
|
+
startIndex,
|
|
1868
|
+
endIndex,
|
|
1869
|
+
suggestion: correct,
|
|
1870
|
+
message: `'${rawWord}' sözcüğünde ünlü düşmesi yapılmaz.`,
|
|
1871
|
+
});
|
|
1872
|
+
flagged = true;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// 7. General Spell Check
|
|
1571
1876
|
if (!flagged) {
|
|
1572
1877
|
const check = await this.checkSpelling(rawWord);
|
|
1573
1878
|
if (!check.isCorrect) {
|
|
@@ -1578,13 +1883,15 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1578
1883
|
endIndex,
|
|
1579
1884
|
suggestion: check.suggestion,
|
|
1580
1885
|
message: check.suggestion
|
|
1581
|
-
? `'${rawWord}' yanlış yazılmış olabilir
|
|
1886
|
+
? `'${rawWord}' yanlış yazılmış olabilir.`
|
|
1582
1887
|
: `'${rawWord}' sözlükte bulunamadı.`,
|
|
1583
1888
|
});
|
|
1584
1889
|
}
|
|
1585
1890
|
}
|
|
1586
1891
|
}
|
|
1587
1892
|
|
|
1893
|
+
issues.sort((a, b) => a.startIndex - b.startIndex);
|
|
1894
|
+
|
|
1588
1895
|
return {
|
|
1589
1896
|
text,
|
|
1590
1897
|
issues,
|
package/test/proofread.test.js
CHANGED
|
@@ -51,6 +51,34 @@ async function runTests() {
|
|
|
51
51
|
assert.strictEqual(combo.issues.some((i) => i.type === "question_particle"), true);
|
|
52
52
|
console.log(" ✓ Combined proofread passed.");
|
|
53
53
|
|
|
54
|
+
// 5. Şey detachment check (herşey, hersey, birşeyler -> her şey, bir şeyler)
|
|
55
|
+
console.log("5. Testing -şey detachment...");
|
|
56
|
+
const s1 = await TDK.proofread("burda herşey yolunda");
|
|
57
|
+
assert.strictEqual(s1.isCorrect, false);
|
|
58
|
+
assert.strictEqual(s1.issues.length, 2);
|
|
59
|
+
assert.strictEqual(s1.issues[0].word, "burda");
|
|
60
|
+
assert.strictEqual(s1.issues[0].suggestion, "burada");
|
|
61
|
+
assert.strictEqual(s1.issues[1].word, "herşey");
|
|
62
|
+
assert.strictEqual(s1.issues[1].suggestion, "her şey");
|
|
63
|
+
|
|
64
|
+
const s2 = await TDK.proofread("hersey çok güzel");
|
|
65
|
+
assert.strictEqual(s2.isCorrect, false);
|
|
66
|
+
assert.strictEqual(s2.issues[0].word, "hersey");
|
|
67
|
+
assert.strictEqual(s2.issues[0].suggestion, "her şey");
|
|
68
|
+
console.log(" ✓ -şey detachment passed.");
|
|
69
|
+
|
|
70
|
+
// 6. Erroneously separated compound phrases (hiç bir, bir çok, git gide)
|
|
71
|
+
console.log("6. Testing compound phrases...");
|
|
72
|
+
const p1 = await TDK.proofread("hiç bir şey bilmiyor");
|
|
73
|
+
assert.strictEqual(p1.isCorrect, false);
|
|
74
|
+
assert.strictEqual(p1.issues[0].word, "hiç bir");
|
|
75
|
+
assert.strictEqual(p1.issues[0].suggestion, "hiçbir");
|
|
76
|
+
|
|
77
|
+
const p2 = await TDK.proofread("yada gelme");
|
|
78
|
+
assert.strictEqual(p2.isCorrect, false);
|
|
79
|
+
assert.strictEqual(p2.issues[0].suggestion, "ya da");
|
|
80
|
+
console.log(" ✓ Compound phrases passed.");
|
|
81
|
+
|
|
54
82
|
console.log("\n All proofread tests passed successfully!");
|
|
55
83
|
}
|
|
56
84
|
|