tdk-api-wrapper 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -14
- package/dist/{chunk-5TYJDVHK.mjs → chunk-7KJHYRJZ.mjs} +426 -15
- package/dist/cli.js +559 -19
- package/dist/cli.mjs +180 -5
- package/dist/index.d.mts +119 -1
- package/dist/index.d.ts +119 -1
- package/dist/index.js +430 -16
- package/dist/index.mjs +9 -3
- package/package.json +2 -2
- package/src/cli.ts +185 -4
- package/src/index.ts +1 -1
- package/src/morphology.ts +115 -5
- package/src/tdk.ts +428 -12
- 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 +60 -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 [];
|
|
@@ -526,9 +569,14 @@ function getStemCandidates(word2, minStemLength = 2, maxDepth = 4) {
|
|
|
526
569
|
const stem = current.slice(0, -suffix.length);
|
|
527
570
|
const hardened = restoreConsonantSoftening(stem);
|
|
528
571
|
const vowelDropped = restoreVowelDrop(stem);
|
|
529
|
-
const
|
|
572
|
+
const geminated = restoreGemination(stem);
|
|
573
|
+
const isNarrowingSuffix = suffix.startsWith("yor") || suffix.includes("iyor") || suffix.includes("\u0131yor") || suffix.includes("uyor") || suffix.includes("\xFCyor");
|
|
574
|
+
const isDeYeBuffer = (stem === "di" || stem === "yi") && suffix.startsWith("y");
|
|
575
|
+
const narrowed = isNarrowingSuffix || isDeYeBuffer ? restoreVowelNarrowing(stem) : [];
|
|
576
|
+
const isVerbSuffix = isNarrowingSuffix || suffix.includes("ecek") || suffix.includes("acak") || suffix.includes("mi\u015F") || suffix.includes("m\u0131\u015F") || suffix.includes("m\xFC\u015F") || suffix.includes("mu\u015F") || suffix.includes("mek") || suffix.includes("mak") || suffix.includes("erek") || suffix.includes("arak") || suffix.includes("dik") || suffix.includes("d\u0131k") || suffix.includes("duk") || suffix.includes("d\xFCk") || suffix.includes("tik") || suffix.includes("t\u0131k") || suffix.includes("tuk") || suffix.includes("t\xFCk") || suffix.includes("sen") || suffix.includes("san") || suffix.includes("sem") || suffix.includes("sam") || suffix.includes("sek") || suffix.includes("sak");
|
|
577
|
+
const verbalBases = [stem, ...hardened, ...narrowed];
|
|
530
578
|
const infinitives = verbalBases.flatMap((v) => restoreInfinitive(v));
|
|
531
|
-
const variants = [stem, ...hardened, ...vowelDropped, ...
|
|
579
|
+
const variants = [stem, ...hardened, ...vowelDropped, ...geminated, ...narrowed];
|
|
532
580
|
for (const variant of variants) {
|
|
533
581
|
if (!seen.has(variant) && variant !== normalized) {
|
|
534
582
|
seen.add(variant);
|
|
@@ -536,6 +584,14 @@ function getStemCandidates(word2, minStemLength = 2, maxDepth = 4) {
|
|
|
536
584
|
candidatesWithWeight.push({ candidate: variant, baseLength: stem.length });
|
|
537
585
|
}
|
|
538
586
|
}
|
|
587
|
+
for (const inf of infinitives) {
|
|
588
|
+
if (!seen.has(inf) && inf !== normalized) {
|
|
589
|
+
seen.add(inf);
|
|
590
|
+
nextFrontier.push(inf);
|
|
591
|
+
const weight = isVerbSuffix ? stem.length + 5 : stem.length;
|
|
592
|
+
candidatesWithWeight.push({ candidate: inf, baseLength: weight });
|
|
593
|
+
}
|
|
594
|
+
}
|
|
539
595
|
}
|
|
540
596
|
}
|
|
541
597
|
}
|
|
@@ -544,7 +600,7 @@ function getStemCandidates(word2, minStemLength = 2, maxDepth = 4) {
|
|
|
544
600
|
frontier = nextFrontier;
|
|
545
601
|
}
|
|
546
602
|
candidatesWithWeight.sort((a, b) => b.baseLength - a.baseLength);
|
|
547
|
-
return [...new Set(candidatesWithWeight.map((
|
|
603
|
+
return [...new Set(candidatesWithWeight.map((c2) => c2.candidate))];
|
|
548
604
|
}
|
|
549
605
|
|
|
550
606
|
// src/tdk.ts
|
|
@@ -633,6 +689,10 @@ M71DMi+y1+TRSJVClEMwvA4yL++7q9XZx5r5wBRWB4kQTKH5qyoZnDw7iiuh1lID
|
|
|
633
689
|
yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
634
690
|
-----END CERTIFICATE-----`
|
|
635
691
|
];
|
|
692
|
+
// Configuration
|
|
693
|
+
static defaultTimeoutMs = 8e3;
|
|
694
|
+
static defaultRetries = 1;
|
|
695
|
+
static maxCacheSize = 1e3;
|
|
636
696
|
// Cache Mechanism
|
|
637
697
|
static isCacheEnabled = false;
|
|
638
698
|
static wordCache = /* @__PURE__ */ new Map();
|
|
@@ -640,6 +700,19 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
640
700
|
static autocompleteCache = [];
|
|
641
701
|
static autocompleteSet = /* @__PURE__ */ new Set();
|
|
642
702
|
static stemCache = /* @__PURE__ */ new Map();
|
|
703
|
+
/**
|
|
704
|
+
* Configures global client options such as network timeout, retries, and cache size.
|
|
705
|
+
*/
|
|
706
|
+
static configure(config) {
|
|
707
|
+
if (config.timeoutMs !== void 0)
|
|
708
|
+
this.defaultTimeoutMs = Math.max(100, config.timeoutMs);
|
|
709
|
+
if (config.retries !== void 0)
|
|
710
|
+
this.defaultRetries = Math.max(0, config.retries);
|
|
711
|
+
if (config.cache !== void 0)
|
|
712
|
+
this.enableCache(config.cache);
|
|
713
|
+
if (config.maxCacheSize !== void 0)
|
|
714
|
+
this.maxCacheSize = Math.max(10, config.maxCacheSize);
|
|
715
|
+
}
|
|
643
716
|
/**
|
|
644
717
|
* Enables or disables in-memory caching for API requests.
|
|
645
718
|
*/
|
|
@@ -659,9 +732,50 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
659
732
|
this.autocompleteSet.clear();
|
|
660
733
|
this.stemCache.clear();
|
|
661
734
|
}
|
|
735
|
+
static setBoundedCache(map, key, value) {
|
|
736
|
+
if (map.size >= this.maxCacheSize) {
|
|
737
|
+
const firstKey = map.keys().next().value;
|
|
738
|
+
if (firstKey !== void 0)
|
|
739
|
+
map.delete(firstKey);
|
|
740
|
+
}
|
|
741
|
+
map.set(key, value);
|
|
742
|
+
}
|
|
662
743
|
static delay(ms) {
|
|
663
744
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
664
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* Internal helper that performs HTTP fetch with timeout and automatic retry on network/5xx errors.
|
|
748
|
+
*/
|
|
749
|
+
static async fetchWithRetry(url, options = {}, retries = this.defaultRetries, timeoutMs = this.defaultTimeoutMs) {
|
|
750
|
+
let lastError;
|
|
751
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
752
|
+
try {
|
|
753
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
754
|
+
const headers = {
|
|
755
|
+
"User-Agent": "TDK-API-Nodejs-Wrapper/1.0",
|
|
756
|
+
...options.headers || {}
|
|
757
|
+
};
|
|
758
|
+
const res = await fetch(url, { ...options, headers, signal });
|
|
759
|
+
if (res.ok || res.status >= 400 && res.status < 500) {
|
|
760
|
+
return res;
|
|
761
|
+
}
|
|
762
|
+
if (attempt < retries) {
|
|
763
|
+
await this.delay(200 * (attempt + 1));
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
return res;
|
|
767
|
+
} catch (err) {
|
|
768
|
+
lastError = err;
|
|
769
|
+
if (attempt < retries) {
|
|
770
|
+
await this.delay(200 * (attempt + 1));
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
throw new TDKNetworkError(`Request to ${url} failed after ${retries + 1} attempts.`, {
|
|
776
|
+
cause: lastError
|
|
777
|
+
});
|
|
778
|
+
}
|
|
665
779
|
/**
|
|
666
780
|
* Fetches detailed information for a given word from the TDK Dictionary.
|
|
667
781
|
*/
|
|
@@ -676,9 +790,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
676
790
|
const url = `${this.BASE_URL}/gts?ara=${encodeURIComponent(cleanWord)}`;
|
|
677
791
|
let response;
|
|
678
792
|
try {
|
|
679
|
-
response = await
|
|
680
|
-
headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" }
|
|
681
|
-
});
|
|
793
|
+
response = await this.fetchWithRetry(url);
|
|
682
794
|
} catch (error) {
|
|
683
795
|
throw new TDKNetworkError("Failed to fetch word from TDK: request failed.", { cause: error });
|
|
684
796
|
}
|
|
@@ -695,12 +807,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
695
807
|
}
|
|
696
808
|
if (!Array.isArray(data) && data && "error" in data) {
|
|
697
809
|
if (this.isCacheEnabled)
|
|
698
|
-
this.wordCache
|
|
810
|
+
this.setBoundedCache(this.wordCache, cleanWord, []);
|
|
699
811
|
return [];
|
|
700
812
|
}
|
|
701
813
|
const results = data;
|
|
702
814
|
if (this.isCacheEnabled) {
|
|
703
|
-
this.wordCache
|
|
815
|
+
this.setBoundedCache(this.wordCache, cleanWord, results);
|
|
704
816
|
}
|
|
705
817
|
return results;
|
|
706
818
|
}
|
|
@@ -827,17 +939,17 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
827
939
|
return this.stemCache.get(clean);
|
|
828
940
|
}
|
|
829
941
|
if (await this.isHeadword(clean)) {
|
|
830
|
-
this.stemCache
|
|
942
|
+
this.setBoundedCache(this.stemCache, clean, clean);
|
|
831
943
|
return clean;
|
|
832
944
|
}
|
|
833
945
|
const candidates = getStemCandidates(clean);
|
|
834
946
|
for (const candidate of candidates) {
|
|
835
947
|
if (await this.isHeadword(candidate)) {
|
|
836
|
-
this.stemCache
|
|
948
|
+
this.setBoundedCache(this.stemCache, clean, candidate);
|
|
837
949
|
return candidate;
|
|
838
950
|
}
|
|
839
951
|
}
|
|
840
|
-
this.stemCache
|
|
952
|
+
this.setBoundedCache(this.stemCache, clean, null);
|
|
841
953
|
return null;
|
|
842
954
|
}
|
|
843
955
|
/**
|
|
@@ -1513,14 +1625,16 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1513
1625
|
meaningCount: meaningsA.length,
|
|
1514
1626
|
origin: originA,
|
|
1515
1627
|
syllables: this.syllabicate(a),
|
|
1516
|
-
harmony: this.checkVowelHarmony(a)
|
|
1628
|
+
harmony: this.checkVowelHarmony(a),
|
|
1629
|
+
labialHarmony: this.checkLabialHarmony(a)
|
|
1517
1630
|
},
|
|
1518
1631
|
b: {
|
|
1519
1632
|
word: b,
|
|
1520
1633
|
meaningCount: meaningsB.length,
|
|
1521
1634
|
origin: originB,
|
|
1522
1635
|
syllables: this.syllabicate(b),
|
|
1523
|
-
harmony: this.checkVowelHarmony(b)
|
|
1636
|
+
harmony: this.checkVowelHarmony(b),
|
|
1637
|
+
labialHarmony: this.checkLabialHarmony(b)
|
|
1524
1638
|
}
|
|
1525
1639
|
};
|
|
1526
1640
|
}
|
|
@@ -1651,9 +1765,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1651
1765
|
}
|
|
1652
1766
|
/**
|
|
1653
1767
|
* Syllabicates a Turkish word based on general grammar rules.
|
|
1768
|
+
* Handles syllable separation for vowels, single consonants, double consonants,
|
|
1769
|
+
* and western loanword three-consonant clusters (e.g. e-lek-trik, kon-trol, or-kes-tra).
|
|
1654
1770
|
*/
|
|
1655
1771
|
static syllabicate(word2) {
|
|
1656
1772
|
const vowels = /[aeıioöuüAEIİOÖUÜ]/;
|
|
1773
|
+
const ONSET_CLUSTERS = /* @__PURE__ */ new Set(["tr", "pr", "kr", "gr", "br", "fr", "dr", "pl", "kl", "fl", "bl", "gl"]);
|
|
1657
1774
|
const result = [];
|
|
1658
1775
|
let currentSyllable = "";
|
|
1659
1776
|
for (let i = word2.length - 1; i >= 0; i--) {
|
|
@@ -1664,8 +1781,13 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1664
1781
|
currentSyllable = word2[i - 1] + currentSyllable;
|
|
1665
1782
|
i--;
|
|
1666
1783
|
} else if (i - 2 >= 0 && !vowels.test(word2[i - 2])) {
|
|
1667
|
-
|
|
1668
|
-
|
|
1784
|
+
if (i - 3 >= 0 && !vowels.test(word2[i - 3]) && ONSET_CLUSTERS.has((word2[i - 2] + word2[i - 1]).toLowerCase())) {
|
|
1785
|
+
currentSyllable = word2[i - 2] + word2[i - 1] + currentSyllable;
|
|
1786
|
+
i -= 2;
|
|
1787
|
+
} else {
|
|
1788
|
+
currentSyllable = word2[i - 1] + currentSyllable;
|
|
1789
|
+
i--;
|
|
1790
|
+
}
|
|
1669
1791
|
}
|
|
1670
1792
|
}
|
|
1671
1793
|
result.unshift(currentSyllable);
|
|
@@ -1695,12 +1817,264 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
|
|
|
1695
1817
|
const hasFront = frontVowels.test(lower);
|
|
1696
1818
|
return !(hasBack && hasFront);
|
|
1697
1819
|
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Checks if a word follows Turkish Minor Vowel Harmony (Küçük Ünlü Uyumu / Labial Harmony).
|
|
1822
|
+
* Rules:
|
|
1823
|
+
* 1. After an unrounded vowel (a, e, ı, i), only unrounded vowels (a, e, ı, i) can follow.
|
|
1824
|
+
* 2. After a rounded vowel (o, ö, u, ü), either an unrounded wide (a, e) or rounded narrow (u, ü) vowel can follow.
|
|
1825
|
+
* Single-syllable words and words with <=1 vowel are considered compliant by convention.
|
|
1826
|
+
*/
|
|
1827
|
+
static checkLabialHarmony(word2) {
|
|
1828
|
+
const lower = word2.toLocaleLowerCase("tr-TR");
|
|
1829
|
+
const vowels = lower.split("").filter((ch) => "ae\u0131io\xF6u\xFC".includes(ch));
|
|
1830
|
+
if (vowels.length <= 1)
|
|
1831
|
+
return true;
|
|
1832
|
+
for (let i = 0; i < vowels.length - 1; i++) {
|
|
1833
|
+
const v1 = vowels[i];
|
|
1834
|
+
const v2 = vowels[i + 1];
|
|
1835
|
+
if ("ae\u0131i".includes(v1)) {
|
|
1836
|
+
if (!"ae\u0131i".includes(v2))
|
|
1837
|
+
return false;
|
|
1838
|
+
} else if ("o\xF6u\xFC".includes(v1)) {
|
|
1839
|
+
if (!"aeu\xFC".includes(v2))
|
|
1840
|
+
return false;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
return true;
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Searches TDK headwords using a wildcard / pattern string.
|
|
1847
|
+
* Wildcards:
|
|
1848
|
+
* '_' or '?' matches any single character
|
|
1849
|
+
* '*' matches zero or more characters
|
|
1850
|
+
* Example: "k_l_m" matches "kalem", "kelam", "kilim".
|
|
1851
|
+
* Runs in-memory against TDK's 81k headword list.
|
|
1852
|
+
*/
|
|
1853
|
+
static async patternSearch(pattern, options) {
|
|
1854
|
+
if (!pattern || pattern.trim() === "")
|
|
1855
|
+
return [];
|
|
1856
|
+
await this.ensureAutocompleteLoaded();
|
|
1857
|
+
const cleanPattern = pattern.trim().toLocaleLowerCase("tr-TR");
|
|
1858
|
+
const escaped = cleanPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/[_?]/g, "[\\p{L}]").replace(/\*/g, "[\\p{L}]*");
|
|
1859
|
+
const regex = new RegExp(`^${escaped}$`, "u");
|
|
1860
|
+
const max = options?.maxResults ?? 50;
|
|
1861
|
+
const matches = [];
|
|
1862
|
+
for (const headword of this.autocompleteCache) {
|
|
1863
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
1864
|
+
if (regex.test(lower)) {
|
|
1865
|
+
matches.push(headword);
|
|
1866
|
+
if (matches.length >= max)
|
|
1867
|
+
break;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
return matches;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Finds headwords in TDK that can be formed from the given letters (anagrams).
|
|
1874
|
+
* If exact-length anagrams exist, they are returned.
|
|
1875
|
+
* If none exist (or exactLength is false), valid sub-anagrams (words using a subset of the letters,
|
|
1876
|
+
* minimum 3 letters) are returned, sorted by length descending.
|
|
1877
|
+
*/
|
|
1878
|
+
static async findAnagrams(letters, options) {
|
|
1879
|
+
if (!letters || letters.trim() === "")
|
|
1880
|
+
return [];
|
|
1881
|
+
await this.ensureAutocompleteLoaded();
|
|
1882
|
+
const clean = letters.trim().toLocaleLowerCase("tr-TR").replace(/[^a-zçğıöşüâîû]/gi, "");
|
|
1883
|
+
if (clean.length === 0)
|
|
1884
|
+
return [];
|
|
1885
|
+
const forceExact = options?.exactLength === true;
|
|
1886
|
+
const max = options?.maxResults ?? 50;
|
|
1887
|
+
const getFrequency = (str) => {
|
|
1888
|
+
const freq = {};
|
|
1889
|
+
for (const ch of str) {
|
|
1890
|
+
freq[ch] = (freq[ch] || 0) + 1;
|
|
1891
|
+
}
|
|
1892
|
+
return freq;
|
|
1893
|
+
};
|
|
1894
|
+
const targetFreq = getFrequency(clean);
|
|
1895
|
+
const exactMatches = [];
|
|
1896
|
+
const subMatches = [];
|
|
1897
|
+
for (const headword of this.autocompleteCache) {
|
|
1898
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
1899
|
+
if (lower.includes(" ") || lower.includes("-"))
|
|
1900
|
+
continue;
|
|
1901
|
+
if (lower.length > clean.length || lower.length < 3)
|
|
1902
|
+
continue;
|
|
1903
|
+
const wordFreq = getFrequency(lower);
|
|
1904
|
+
let isValid = true;
|
|
1905
|
+
for (const [ch, count] of Object.entries(wordFreq)) {
|
|
1906
|
+
if (!targetFreq[ch] || targetFreq[ch] < count) {
|
|
1907
|
+
isValid = false;
|
|
1908
|
+
break;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
if (isValid && lower !== clean) {
|
|
1912
|
+
if (lower.length === clean.length) {
|
|
1913
|
+
exactMatches.push(headword);
|
|
1914
|
+
} else {
|
|
1915
|
+
subMatches.push(headword);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
if (exactMatches.length > 0 || forceExact) {
|
|
1920
|
+
return exactMatches.slice(0, max);
|
|
1921
|
+
}
|
|
1922
|
+
subMatches.sort((a, b) => b.length - a.length || a.localeCompare(b, "tr-TR"));
|
|
1923
|
+
return subMatches.slice(0, max);
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Finds words in TDK that rhyme with the given word (sharing the same ending suffix/letters).
|
|
1927
|
+
* @param word The target word
|
|
1928
|
+
* @param options.minLetters Minimum number of ending characters that must match (default: 3)
|
|
1929
|
+
* @param options.maxResults Maximum number of rhyme results to return (default: 50)
|
|
1930
|
+
*/
|
|
1931
|
+
static async findRhymes(word2, options) {
|
|
1932
|
+
if (!word2 || word2.trim() === "")
|
|
1933
|
+
return [];
|
|
1934
|
+
await this.ensureAutocompleteLoaded();
|
|
1935
|
+
const clean = word2.trim().toLocaleLowerCase("tr-TR");
|
|
1936
|
+
const minLetters = Math.min(options?.minLetters ?? 3, clean.length);
|
|
1937
|
+
const max = options?.maxResults ?? 50;
|
|
1938
|
+
const suffix = clean.slice(-minLetters);
|
|
1939
|
+
const results = [];
|
|
1940
|
+
for (const headword of this.autocompleteCache) {
|
|
1941
|
+
const lower = headword.toLocaleLowerCase("tr-TR");
|
|
1942
|
+
if (lower !== clean && lower.endsWith(suffix) && !lower.includes(" ")) {
|
|
1943
|
+
results.push(headword);
|
|
1944
|
+
if (results.length >= max)
|
|
1945
|
+
break;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
return results;
|
|
1949
|
+
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Performs comprehensive spelling, grammar, and syntax proofreading on a Turkish text.
|
|
1952
|
+
* Detects:
|
|
1953
|
+
* 1. Conjunction 'da/de' erroneously joined to verbs or words (e.g. "gitsende" -> "gitsen de")
|
|
1954
|
+
* 2. Conjunction 'ki' erroneously joined to verbs (e.g. "gördümki" -> "gördüm ki"), respecting SOMBAHÇEMİ exceptions
|
|
1955
|
+
* 3. Question particle 'mi/mı/mu/mü' erroneously joined to words (e.g. "geldimi" -> "geldi mi")
|
|
1956
|
+
* 4. Misspelled words with dictionary suggestions (via edit-distance & morphology)
|
|
1957
|
+
*/
|
|
1958
|
+
static async proofread(text) {
|
|
1959
|
+
if (!text || text.trim() === "") {
|
|
1960
|
+
return { text: text || "", issues: [], isCorrect: true };
|
|
1961
|
+
}
|
|
1962
|
+
await this.ensureAutocompleteLoaded();
|
|
1963
|
+
const issues = [];
|
|
1964
|
+
const SOMBAHCEMI = /* @__PURE__ */ new Set([
|
|
1965
|
+
"sanki",
|
|
1966
|
+
"oysaki",
|
|
1967
|
+
"mademki",
|
|
1968
|
+
"belki",
|
|
1969
|
+
"halbuki",
|
|
1970
|
+
"\xE7\xFCnk\xFC",
|
|
1971
|
+
"me\u011Ferki",
|
|
1972
|
+
"illaki"
|
|
1973
|
+
]);
|
|
1974
|
+
const tokenRegex = /[\p{L}0-9'’]+/gu;
|
|
1975
|
+
let match;
|
|
1976
|
+
while ((match = tokenRegex.exec(text)) !== null) {
|
|
1977
|
+
const rawWord = match[0];
|
|
1978
|
+
const startIndex = match.index;
|
|
1979
|
+
const endIndex = startIndex + rawWord.length;
|
|
1980
|
+
const lower = rawWord.toLocaleLowerCase("tr-TR");
|
|
1981
|
+
if (/^\d+$/.test(lower))
|
|
1982
|
+
continue;
|
|
1983
|
+
let flagged = false;
|
|
1984
|
+
const questionMatch = lower.match(/^(.+?)(m[ıiuü](?:sin|sın|sun|sün|siniz|sınız|sunuz|sünüz|yiz|yız|yuz|yüz|m|k)?)$/);
|
|
1985
|
+
if (questionMatch) {
|
|
1986
|
+
const base = questionMatch[1];
|
|
1987
|
+
const particle = questionMatch[2];
|
|
1988
|
+
if (base.length >= 2 && (await this.isHeadword(base) || await this.findRoot(base) !== null)) {
|
|
1989
|
+
if (!await this.isHeadword(lower)) {
|
|
1990
|
+
issues.push({
|
|
1991
|
+
type: "question_particle",
|
|
1992
|
+
word: rawWord,
|
|
1993
|
+
startIndex,
|
|
1994
|
+
endIndex,
|
|
1995
|
+
suggestion: `${base} ${particle}`,
|
|
1996
|
+
message: `'${particle}' soru eki kendinden \xF6nceki kelimeden ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
|
|
1997
|
+
});
|
|
1998
|
+
flagged = true;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
const VERB_CONJUGATION_REGEX = /(?:d[ıiuü][kmmn]?|t[ıiuü][kmmn]?|d[ıiuü]n[ıiuü]z?|t[ıiuü]n[ıiuü]z?|m[ıiuü]ş(?:[szn][ıiuü]z?|lar)?|yor(?:um|sun|uz|lar)?|ecek(?:sin|iz|ler)?|acak(?:sın|ız|lar)?|s[ae][mnk]|s[ae]n[ıiz]?|meli|malı|me[mz]|ma[mz])$/i;
|
|
2003
|
+
if (!flagged && lower.endsWith("ki") && lower.length > 3) {
|
|
2004
|
+
const base = lower.slice(0, -2);
|
|
2005
|
+
if (!SOMBAHCEMI.has(lower)) {
|
|
2006
|
+
if (!await this.isHeadword(lower)) {
|
|
2007
|
+
const root = await this.findRoot(base);
|
|
2008
|
+
const isVerb = root && (root.endsWith("mek") || root.endsWith("mak")) || base === "demek" || base === "kald\u0131" || base === "yeter" || base === "bilmem" || VERB_CONJUGATION_REGEX.test(base);
|
|
2009
|
+
if (isVerb) {
|
|
2010
|
+
issues.push({
|
|
2011
|
+
type: "conjunction_ki",
|
|
2012
|
+
word: rawWord,
|
|
2013
|
+
startIndex,
|
|
2014
|
+
endIndex,
|
|
2015
|
+
suggestion: `${base} ki`,
|
|
2016
|
+
message: `'ki' ba\u011Flac\u0131 ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
|
|
2017
|
+
});
|
|
2018
|
+
flagged = true;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
if (!flagged && (lower.endsWith("de") || lower.endsWith("da") || lower.endsWith("te") || lower.endsWith("ta")) && lower.length > 3) {
|
|
2024
|
+
const base = lower.slice(0, -2);
|
|
2025
|
+
const ending = lower.slice(-2);
|
|
2026
|
+
if (!await this.isHeadword(lower)) {
|
|
2027
|
+
const root = await this.findRoot(base);
|
|
2028
|
+
const isVerb = root && (root.endsWith("mek") || root.endsWith("mak")) || VERB_CONJUGATION_REGEX.test(base);
|
|
2029
|
+
if (isVerb) {
|
|
2030
|
+
const correctEnding = ending.startsWith("t") ? ending === "te" ? "de" : "da" : ending;
|
|
2031
|
+
issues.push({
|
|
2032
|
+
type: "conjunction_da",
|
|
2033
|
+
word: rawWord,
|
|
2034
|
+
startIndex,
|
|
2035
|
+
endIndex,
|
|
2036
|
+
suggestion: `${base} ${correctEnding}`,
|
|
2037
|
+
message: `'da/de' ba\u011Flac\u0131 fiillerden sonra her zaman ayr\u0131 yaz\u0131l\u0131r (ba\u011Fla\xE7 olan da/de sertle\u015Fmez).`
|
|
2038
|
+
});
|
|
2039
|
+
flagged = true;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
if (!flagged) {
|
|
2044
|
+
const check = await this.checkSpelling(rawWord);
|
|
2045
|
+
if (!check.isCorrect) {
|
|
2046
|
+
issues.push({
|
|
2047
|
+
type: "spelling",
|
|
2048
|
+
word: rawWord,
|
|
2049
|
+
startIndex,
|
|
2050
|
+
endIndex,
|
|
2051
|
+
suggestion: check.suggestion,
|
|
2052
|
+
message: check.suggestion ? `'${rawWord}' yanl\u0131\u015F yaz\u0131lm\u0131\u015F olabilir. \xD6neri: '${check.suggestion}'` : `'${rawWord}' s\xF6zl\xFCkte bulunamad\u0131.`
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
return {
|
|
2058
|
+
text,
|
|
2059
|
+
issues,
|
|
2060
|
+
isCorrect: issues.length === 0
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
1698
2063
|
};
|
|
1699
2064
|
|
|
1700
2065
|
// src/cli.ts
|
|
1701
2066
|
var rawArgs = process.argv.slice(2);
|
|
1702
2067
|
var jsonMode = rawArgs.includes("--json");
|
|
1703
2068
|
var args = rawArgs.filter((a) => a !== "--json");
|
|
2069
|
+
var isColor = !jsonMode && Boolean(process.stdout.isTTY);
|
|
2070
|
+
var c = {
|
|
2071
|
+
bold: (s) => isColor ? `\x1B[1m${s}\x1B[0m` : s,
|
|
2072
|
+
dim: (s) => isColor ? `\x1B[2m${s}\x1B[0m` : s,
|
|
2073
|
+
green: (s) => isColor ? `\x1B[32m${s}\x1B[0m` : s,
|
|
2074
|
+
yellow: (s) => isColor ? `\x1B[33m${s}\x1B[0m` : s,
|
|
2075
|
+
cyan: (s) => isColor ? `\x1B[36m${s}\x1B[0m` : s,
|
|
2076
|
+
red: (s) => isColor ? `\x1B[31m${s}\x1B[0m` : s
|
|
2077
|
+
};
|
|
1704
2078
|
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
1705
2079
|
"ara",
|
|
1706
2080
|
"anlam",
|
|
@@ -1708,6 +2082,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
1708
2082
|
"ornek",
|
|
1709
2083
|
"hece",
|
|
1710
2084
|
"uyum",
|
|
2085
|
+
"kucukuyum",
|
|
1711
2086
|
"yazim",
|
|
1712
2087
|
"kok",
|
|
1713
2088
|
"stem",
|
|
@@ -1722,6 +2097,14 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
1722
2097
|
"karsilastir",
|
|
1723
2098
|
"analiz",
|
|
1724
2099
|
"oneri",
|
|
2100
|
+
"bulmaca",
|
|
2101
|
+
"pattern",
|
|
2102
|
+
"anagram",
|
|
2103
|
+
"kafiye",
|
|
2104
|
+
"rhyme",
|
|
2105
|
+
"denetle",
|
|
2106
|
+
"proofread",
|
|
2107
|
+
"repl",
|
|
1725
2108
|
"kubbealti",
|
|
1726
2109
|
"nisanyan",
|
|
1727
2110
|
"viki"
|
|
@@ -1743,17 +2126,93 @@ function printError(message) {
|
|
|
1743
2126
|
if (jsonMode) {
|
|
1744
2127
|
console.log(JSON.stringify({ error: message }));
|
|
1745
2128
|
} else {
|
|
1746
|
-
console.log(`Hata: ${message}`);
|
|
2129
|
+
console.log(c.red(`Hata: ${message}`));
|
|
1747
2130
|
}
|
|
1748
2131
|
}
|
|
2132
|
+
async function startRepl() {
|
|
2133
|
+
const readline = await import("readline");
|
|
2134
|
+
const rl = readline.createInterface({
|
|
2135
|
+
input: process.stdin,
|
|
2136
|
+
output: process.stdout,
|
|
2137
|
+
prompt: c.cyan("tdk> ")
|
|
2138
|
+
});
|
|
2139
|
+
console.log(c.bold("TDK \u0130nteraktif S\xF6zl\xFCk Kabu\u011Fu (\xC7\u0131kmak i\xE7in 'exit' veya Ctrl+C)"));
|
|
2140
|
+
console.log(c.dim("Komutlar: ara <kelime>, hece <kelime>, bulmaca <desen>, denetle <metin> veya do\u011Frudan kelime"));
|
|
2141
|
+
rl.prompt();
|
|
2142
|
+
rl.on("line", async (line) => {
|
|
2143
|
+
const trimmed = line.trim();
|
|
2144
|
+
if (!trimmed) {
|
|
2145
|
+
rl.prompt();
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
if (trimmed === "exit" || trimmed === "quit" || trimmed === ".exit") {
|
|
2149
|
+
rl.close();
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
const parts = trimmed.split(/\s+/);
|
|
2153
|
+
let subCmd = parts[0].toLowerCase();
|
|
2154
|
+
let subArg = parts.slice(1).join(" ");
|
|
2155
|
+
if (!KNOWN_COMMANDS.has(subCmd)) {
|
|
2156
|
+
subArg = trimmed;
|
|
2157
|
+
subCmd = "anlam";
|
|
2158
|
+
}
|
|
2159
|
+
try {
|
|
2160
|
+
if (subCmd === "ara" || subCmd === "anlam") {
|
|
2161
|
+
const meanings = await TDK.getMeanings(subArg);
|
|
2162
|
+
if (meanings.length === 0)
|
|
2163
|
+
console.log(c.dim("Sonu\xE7 bulunamad\u0131."));
|
|
2164
|
+
else
|
|
2165
|
+
meanings.forEach((m, i) => console.log(`${i + 1}. ${c.green(m)}`));
|
|
2166
|
+
} else if (subCmd === "koken") {
|
|
2167
|
+
const origin = await TDK.getOrigin(subArg);
|
|
2168
|
+
console.log(`K\xF6ken: ${c.cyan(origin || "Bilinmiyor")}`);
|
|
2169
|
+
} else if (subCmd === "hece") {
|
|
2170
|
+
const s = TDK.syllabicate(subArg);
|
|
2171
|
+
console.log(`Heceler: ${c.yellow(s.join("-"))}`);
|
|
2172
|
+
} else if (subCmd === "uyum") {
|
|
2173
|
+
const h = TDK.checkVowelHarmony(subArg);
|
|
2174
|
+
console.log(`B\xFCy\xFCk \xDCnl\xFC Uyumu: ${h ? c.green("Uyar") : c.red("Uymaz")}`);
|
|
2175
|
+
} else if (subCmd === "kucukuyum") {
|
|
2176
|
+
const h = TDK.checkLabialHarmony(subArg);
|
|
2177
|
+
console.log(`K\xFC\xE7\xFCk \xDCnl\xFC Uyumu: ${h ? c.green("Uyar") : c.red("Uymaz")}`);
|
|
2178
|
+
} else if (subCmd === "bulmaca" || subCmd === "pattern") {
|
|
2179
|
+
const matches = await TDK.patternSearch(subArg);
|
|
2180
|
+
console.log(matches.slice(0, 15).join(", "));
|
|
2181
|
+
} else if (subCmd === "denetle" || subCmd === "proofread") {
|
|
2182
|
+
const res = await TDK.proofread(subArg);
|
|
2183
|
+
if (res.isCorrect)
|
|
2184
|
+
console.log(c.green("\u2713 Sorun bulunamad\u0131."));
|
|
2185
|
+
else
|
|
2186
|
+
res.issues.forEach((iss) => console.log(`- ${c.yellow(iss.word)}: ${iss.message}${iss.suggestion ? " -> " + c.green(iss.suggestion) : ""}`));
|
|
2187
|
+
} else {
|
|
2188
|
+
console.log(c.dim("\xD6rnek komutlar: 'ara kalem', 'hece elektrik', 'bulmaca k_l_m', 'denetle Bug\xFCn evdeyim'"));
|
|
2189
|
+
}
|
|
2190
|
+
} catch (e) {
|
|
2191
|
+
console.log(c.red(`Hata: ${e?.message || e}`));
|
|
2192
|
+
}
|
|
2193
|
+
rl.prompt();
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
1749
2196
|
async function run() {
|
|
1750
|
-
if (!command
|
|
2197
|
+
if (!command) {
|
|
2198
|
+
if (process.stdin.isTTY) {
|
|
2199
|
+
await startRepl();
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
1751
2202
|
console.log("Kullan\u0131m: tdk [komut] <kelime> [--json]");
|
|
1752
2203
|
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"
|
|
2204
|
+
"Komutlar: ara, anlam, koken, ornek, hece, uyum, kucukuyum, yazim, kok, deyim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, bulmaca, anagram, kafiye, denetle, repl, kubbealti, nisanyan, viki"
|
|
1754
2205
|
);
|
|
1755
2206
|
console.log("Not: Komut belirtilmezse do\u011Frudan kelime anlam\u0131 aran\u0131r (\xF6rn: tdk selam)");
|
|
1756
|
-
process.exit(
|
|
2207
|
+
process.exit(1);
|
|
2208
|
+
}
|
|
2209
|
+
if (command === "--help" || command === "-h") {
|
|
2210
|
+
console.log("Kullan\u0131m: tdk [komut] <kelime> [--json]");
|
|
2211
|
+
console.log(
|
|
2212
|
+
"Komutlar: ara, anlam, koken, ornek, hece, uyum, kucukuyum, yazim, kok, deyim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, bulmaca, anagram, kafiye, denetle, repl, kubbealti, nisanyan, viki"
|
|
2213
|
+
);
|
|
2214
|
+
console.log("Not: Komut belirtilmezse do\u011Frudan kelime anlam\u0131 aran\u0131r (\xF6rn: tdk selam)");
|
|
2215
|
+
process.exit(0);
|
|
1757
2216
|
}
|
|
1758
2217
|
TDK.enableCache(false);
|
|
1759
2218
|
try {
|
|
@@ -1812,6 +2271,17 @@ async function run() {
|
|
|
1812
2271
|
);
|
|
1813
2272
|
break;
|
|
1814
2273
|
}
|
|
2274
|
+
case "kucukuyum":
|
|
2275
|
+
case "labial": {
|
|
2276
|
+
if (!word)
|
|
2277
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
2278
|
+
const isHarmony = TDK.checkLabialHarmony(word);
|
|
2279
|
+
printResult(
|
|
2280
|
+
{ word, labialHarmony: isHarmony },
|
|
2281
|
+
() => console.log(`K\xFC\xE7\xFCk \xDCnl\xFC Uyumu: ${isHarmony ? "Uyar" : "Uymaz"}`)
|
|
2282
|
+
);
|
|
2283
|
+
break;
|
|
2284
|
+
}
|
|
1815
2285
|
case "yazim": {
|
|
1816
2286
|
if (!word)
|
|
1817
2287
|
throw new Error("Kelime belirtmelisiniz.");
|
|
@@ -1987,6 +2457,76 @@ async function run() {
|
|
|
1987
2457
|
});
|
|
1988
2458
|
break;
|
|
1989
2459
|
}
|
|
2460
|
+
case "bulmaca":
|
|
2461
|
+
case "pattern": {
|
|
2462
|
+
if (!word)
|
|
2463
|
+
throw new Error("Desen belirtmelisiniz (\xF6rn: k_l_m).");
|
|
2464
|
+
const matches = await TDK.patternSearch(word);
|
|
2465
|
+
printResult(matches, () => {
|
|
2466
|
+
if (matches.length === 0) {
|
|
2467
|
+
console.log("E\u015Fle\u015Fen kelime bulunamad\u0131.");
|
|
2468
|
+
} else {
|
|
2469
|
+
console.log(c.bold(`Bulunan Kelimeler (${matches.length}):`));
|
|
2470
|
+
matches.forEach((m, i) => console.log(`${i + 1}. ${c.cyan(m)}`));
|
|
2471
|
+
}
|
|
2472
|
+
});
|
|
2473
|
+
break;
|
|
2474
|
+
}
|
|
2475
|
+
case "anagram": {
|
|
2476
|
+
if (!word)
|
|
2477
|
+
throw new Error("Harfler belirtmelisiniz.");
|
|
2478
|
+
const anagrams = await TDK.findAnagrams(word);
|
|
2479
|
+
printResult(anagrams, () => {
|
|
2480
|
+
if (anagrams.length === 0) {
|
|
2481
|
+
console.log("Anagram veya bu harflerle t\xFCretilebilecek kelime bulunamad\u0131.");
|
|
2482
|
+
} else {
|
|
2483
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR").replace(/[^a-zçğıöşüâîû]/gi, "");
|
|
2484
|
+
const hasExact = anagrams.some((a) => a.length === clean.length);
|
|
2485
|
+
const title = hasExact ? `Anagramlar (${anagrams.length}):` : `Birebir anagram bulunamad\u0131. Bu harflerle t\xFCretilen kelimeler (${anagrams.length}):`;
|
|
2486
|
+
console.log(c.bold(title));
|
|
2487
|
+
anagrams.forEach((a, i) => console.log(`${i + 1}. ${c.green(a)} ${c.dim(`(${a.length} harf)`)}`));
|
|
2488
|
+
}
|
|
2489
|
+
});
|
|
2490
|
+
break;
|
|
2491
|
+
}
|
|
2492
|
+
case "kafiye":
|
|
2493
|
+
case "rhyme": {
|
|
2494
|
+
if (!word)
|
|
2495
|
+
throw new Error("Kelime belirtmelisiniz.");
|
|
2496
|
+
const rhymes = await TDK.findRhymes(word);
|
|
2497
|
+
printResult(rhymes, () => {
|
|
2498
|
+
if (rhymes.length === 0) {
|
|
2499
|
+
console.log("Kafiye bulunamad\u0131.");
|
|
2500
|
+
} else {
|
|
2501
|
+
console.log(c.bold(`Kafiyeli Kelimeler (${rhymes.length}):`));
|
|
2502
|
+
rhymes.forEach((r, i) => console.log(`${i + 1}. ${c.yellow(r)}`));
|
|
2503
|
+
}
|
|
2504
|
+
});
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
2507
|
+
case "denetle":
|
|
2508
|
+
case "proofread": {
|
|
2509
|
+
if (!word)
|
|
2510
|
+
throw new Error("Metin belirtmelisiniz.");
|
|
2511
|
+
const result = await TDK.proofread(word);
|
|
2512
|
+
printResult(result, () => {
|
|
2513
|
+
if (result.isCorrect) {
|
|
2514
|
+
console.log(c.green("\u2713 Metinde imla veya ba\u011Fla\xE7 hatas\u0131 tespit edilmedi."));
|
|
2515
|
+
} else {
|
|
2516
|
+
console.log(c.bold(c.red(`Metinde ${result.issues.length} olas\u0131 sorun tespit edildi:`)));
|
|
2517
|
+
result.issues.forEach((issue, i) => {
|
|
2518
|
+
const label = c.yellow(`[${issue.type}]`);
|
|
2519
|
+
const sug = issue.suggestion ? c.green(` -> \xD6neri: ${issue.suggestion}`) : "";
|
|
2520
|
+
console.log(`${i + 1}. ${label} "${c.bold(issue.word)}": ${issue.message}${sug}`);
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
});
|
|
2524
|
+
break;
|
|
2525
|
+
}
|
|
2526
|
+
case "repl": {
|
|
2527
|
+
await startRepl();
|
|
2528
|
+
break;
|
|
2529
|
+
}
|
|
1990
2530
|
case "kubbealti": {
|
|
1991
2531
|
if (!word)
|
|
1992
2532
|
throw new Error("Kelime belirtmelisiniz.");
|