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.
@@ -332,6 +332,12 @@ var TURKISH_SUFFIXES = [
332
332
  "s\u0131n",
333
333
  "sun",
334
334
  "s\xFCn",
335
+ "sen",
336
+ "san",
337
+ "sem",
338
+ "sam",
339
+ "sek",
340
+ "sak",
335
341
  "siz",
336
342
  "s\u0131z",
337
343
  "suz",
@@ -469,6 +475,43 @@ function restoreVowelDrop(stem) {
469
475
  }
470
476
  return [];
471
477
  }
478
+ function restoreGemination(stem) {
479
+ if (stem.length < 3)
480
+ return [];
481
+ const c1 = stem[stem.length - 2];
482
+ const c2 = stem[stem.length - 1];
483
+ if (c1 === c2 && !isVowel(c1)) {
484
+ const single = stem.slice(0, -1);
485
+ const hardened = restoreConsonantSoftening(single);
486
+ return [single, ...hardened];
487
+ }
488
+ return [];
489
+ }
490
+ function restoreVowelNarrowing(stem) {
491
+ if (stem.length < 2)
492
+ return [];
493
+ if (stem === "di")
494
+ return ["de"];
495
+ if (stem === "yi")
496
+ return ["ye"];
497
+ const lastChar = stem[stem.length - 1];
498
+ const isLastNarrow = "\u0131iu\xFC".includes(lastChar);
499
+ if (isLastNarrow) {
500
+ const vowelsInBase = stem.slice(0, -1).split("").filter(isVowel);
501
+ const lastVowel = vowelsInBase.length > 0 ? vowelsInBase[vowelsInBase.length - 1] : lastChar;
502
+ const widened = "a\u0131ou".includes(lastVowel) ? "a" : "e";
503
+ return [stem.slice(0, -1) + widened];
504
+ }
505
+ if (!isVowel(lastChar)) {
506
+ const vowelsInBase = stem.split("").filter(isVowel);
507
+ if (vowelsInBase.length > 0) {
508
+ const lastVowel = vowelsInBase[vowelsInBase.length - 1];
509
+ const widened = "a\u0131ou".includes(lastVowel) ? "a" : "e";
510
+ return [stem + widened];
511
+ }
512
+ }
513
+ return [];
514
+ }
472
515
  function restoreInfinitive(stem) {
473
516
  if (stem.length < 2)
474
517
  return [];
@@ -501,9 +544,14 @@ function getStemCandidates(word, minStemLength = 2, maxDepth = 4) {
501
544
  const stem = current.slice(0, -suffix.length);
502
545
  const hardened = restoreConsonantSoftening(stem);
503
546
  const vowelDropped = restoreVowelDrop(stem);
504
- const verbalBases = [stem, ...hardened];
547
+ const geminated = restoreGemination(stem);
548
+ const isNarrowingSuffix = suffix.startsWith("yor") || suffix.includes("iyor") || suffix.includes("\u0131yor") || suffix.includes("uyor") || suffix.includes("\xFCyor");
549
+ const isDeYeBuffer = (stem === "di" || stem === "yi") && suffix.startsWith("y");
550
+ const narrowed = isNarrowingSuffix || isDeYeBuffer ? restoreVowelNarrowing(stem) : [];
551
+ 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");
552
+ const verbalBases = [stem, ...hardened, ...narrowed];
505
553
  const infinitives = verbalBases.flatMap((v) => restoreInfinitive(v));
506
- const variants = [stem, ...hardened, ...vowelDropped, ...infinitives];
554
+ const variants = [stem, ...hardened, ...vowelDropped, ...geminated, ...narrowed];
507
555
  for (const variant of variants) {
508
556
  if (!seen.has(variant) && variant !== normalized) {
509
557
  seen.add(variant);
@@ -511,6 +559,14 @@ function getStemCandidates(word, minStemLength = 2, maxDepth = 4) {
511
559
  candidatesWithWeight.push({ candidate: variant, baseLength: stem.length });
512
560
  }
513
561
  }
562
+ for (const inf of infinitives) {
563
+ if (!seen.has(inf) && inf !== normalized) {
564
+ seen.add(inf);
565
+ nextFrontier.push(inf);
566
+ const weight = isVerbSuffix ? stem.length + 5 : stem.length;
567
+ candidatesWithWeight.push({ candidate: inf, baseLength: weight });
568
+ }
569
+ }
514
570
  }
515
571
  }
516
572
  }
@@ -608,6 +664,10 @@ M71DMi+y1+TRSJVClEMwvA4yL++7q9XZx5r5wBRWB4kQTKH5qyoZnDw7iiuh1lID
608
664
  yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
609
665
  -----END CERTIFICATE-----`
610
666
  ];
667
+ // Configuration
668
+ static defaultTimeoutMs = 8e3;
669
+ static defaultRetries = 1;
670
+ static maxCacheSize = 1e3;
611
671
  // Cache Mechanism
612
672
  static isCacheEnabled = false;
613
673
  static wordCache = /* @__PURE__ */ new Map();
@@ -615,6 +675,19 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
615
675
  static autocompleteCache = [];
616
676
  static autocompleteSet = /* @__PURE__ */ new Set();
617
677
  static stemCache = /* @__PURE__ */ new Map();
678
+ /**
679
+ * Configures global client options such as network timeout, retries, and cache size.
680
+ */
681
+ static configure(config) {
682
+ if (config.timeoutMs !== void 0)
683
+ this.defaultTimeoutMs = Math.max(100, config.timeoutMs);
684
+ if (config.retries !== void 0)
685
+ this.defaultRetries = Math.max(0, config.retries);
686
+ if (config.cache !== void 0)
687
+ this.enableCache(config.cache);
688
+ if (config.maxCacheSize !== void 0)
689
+ this.maxCacheSize = Math.max(10, config.maxCacheSize);
690
+ }
618
691
  /**
619
692
  * Enables or disables in-memory caching for API requests.
620
693
  */
@@ -634,9 +707,50 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
634
707
  this.autocompleteSet.clear();
635
708
  this.stemCache.clear();
636
709
  }
710
+ static setBoundedCache(map, key, value) {
711
+ if (map.size >= this.maxCacheSize) {
712
+ const firstKey = map.keys().next().value;
713
+ if (firstKey !== void 0)
714
+ map.delete(firstKey);
715
+ }
716
+ map.set(key, value);
717
+ }
637
718
  static delay(ms) {
638
719
  return new Promise((resolve) => setTimeout(resolve, ms));
639
720
  }
721
+ /**
722
+ * Internal helper that performs HTTP fetch with timeout and automatic retry on network/5xx errors.
723
+ */
724
+ static async fetchWithRetry(url, options = {}, retries = this.defaultRetries, timeoutMs = this.defaultTimeoutMs) {
725
+ let lastError;
726
+ for (let attempt = 0; attempt <= retries; attempt++) {
727
+ try {
728
+ const signal = AbortSignal.timeout(timeoutMs);
729
+ const headers = {
730
+ "User-Agent": "TDK-API-Nodejs-Wrapper/1.0",
731
+ ...options.headers || {}
732
+ };
733
+ const res = await fetch(url, { ...options, headers, signal });
734
+ if (res.ok || res.status >= 400 && res.status < 500) {
735
+ return res;
736
+ }
737
+ if (attempt < retries) {
738
+ await this.delay(200 * (attempt + 1));
739
+ continue;
740
+ }
741
+ return res;
742
+ } catch (err) {
743
+ lastError = err;
744
+ if (attempt < retries) {
745
+ await this.delay(200 * (attempt + 1));
746
+ continue;
747
+ }
748
+ }
749
+ }
750
+ throw new TDKNetworkError(`Request to ${url} failed after ${retries + 1} attempts.`, {
751
+ cause: lastError
752
+ });
753
+ }
640
754
  /**
641
755
  * Fetches detailed information for a given word from the TDK Dictionary.
642
756
  */
@@ -651,9 +765,7 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
651
765
  const url = `${this.BASE_URL}/gts?ara=${encodeURIComponent(cleanWord)}`;
652
766
  let response;
653
767
  try {
654
- response = await fetch(url, {
655
- headers: { "User-Agent": "TDK-API-Nodejs-Wrapper/1.0" }
656
- });
768
+ response = await this.fetchWithRetry(url);
657
769
  } catch (error) {
658
770
  throw new TDKNetworkError("Failed to fetch word from TDK: request failed.", { cause: error });
659
771
  }
@@ -670,12 +782,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
670
782
  }
671
783
  if (!Array.isArray(data) && data && "error" in data) {
672
784
  if (this.isCacheEnabled)
673
- this.wordCache.set(cleanWord, []);
785
+ this.setBoundedCache(this.wordCache, cleanWord, []);
674
786
  return [];
675
787
  }
676
788
  const results = data;
677
789
  if (this.isCacheEnabled) {
678
- this.wordCache.set(cleanWord, results);
790
+ this.setBoundedCache(this.wordCache, cleanWord, results);
679
791
  }
680
792
  return results;
681
793
  }
@@ -802,17 +914,17 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
802
914
  return this.stemCache.get(clean);
803
915
  }
804
916
  if (await this.isHeadword(clean)) {
805
- this.stemCache.set(clean, clean);
917
+ this.setBoundedCache(this.stemCache, clean, clean);
806
918
  return clean;
807
919
  }
808
920
  const candidates = getStemCandidates(clean);
809
921
  for (const candidate of candidates) {
810
922
  if (await this.isHeadword(candidate)) {
811
- this.stemCache.set(clean, candidate);
923
+ this.setBoundedCache(this.stemCache, clean, candidate);
812
924
  return candidate;
813
925
  }
814
926
  }
815
- this.stemCache.set(clean, null);
927
+ this.setBoundedCache(this.stemCache, clean, null);
816
928
  return null;
817
929
  }
818
930
  /**
@@ -1488,14 +1600,16 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1488
1600
  meaningCount: meaningsA.length,
1489
1601
  origin: originA,
1490
1602
  syllables: this.syllabicate(a),
1491
- harmony: this.checkVowelHarmony(a)
1603
+ harmony: this.checkVowelHarmony(a),
1604
+ labialHarmony: this.checkLabialHarmony(a)
1492
1605
  },
1493
1606
  b: {
1494
1607
  word: b,
1495
1608
  meaningCount: meaningsB.length,
1496
1609
  origin: originB,
1497
1610
  syllables: this.syllabicate(b),
1498
- harmony: this.checkVowelHarmony(b)
1611
+ harmony: this.checkVowelHarmony(b),
1612
+ labialHarmony: this.checkLabialHarmony(b)
1499
1613
  }
1500
1614
  };
1501
1615
  }
@@ -1626,9 +1740,12 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1626
1740
  }
1627
1741
  /**
1628
1742
  * Syllabicates a Turkish word based on general grammar rules.
1743
+ * Handles syllable separation for vowels, single consonants, double consonants,
1744
+ * and western loanword three-consonant clusters (e.g. e-lek-trik, kon-trol, or-kes-tra).
1629
1745
  */
1630
1746
  static syllabicate(word) {
1631
1747
  const vowels = /[aeıioöuüAEIİOÖUÜ]/;
1748
+ const ONSET_CLUSTERS = /* @__PURE__ */ new Set(["tr", "pr", "kr", "gr", "br", "fr", "dr", "pl", "kl", "fl", "bl", "gl"]);
1632
1749
  const result = [];
1633
1750
  let currentSyllable = "";
1634
1751
  for (let i = word.length - 1; i >= 0; i--) {
@@ -1639,8 +1756,13 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1639
1756
  currentSyllable = word[i - 1] + currentSyllable;
1640
1757
  i--;
1641
1758
  } else if (i - 2 >= 0 && !vowels.test(word[i - 2])) {
1642
- currentSyllable = word[i - 1] + currentSyllable;
1643
- i--;
1759
+ if (i - 3 >= 0 && !vowels.test(word[i - 3]) && ONSET_CLUSTERS.has((word[i - 2] + word[i - 1]).toLowerCase())) {
1760
+ currentSyllable = word[i - 2] + word[i - 1] + currentSyllable;
1761
+ i -= 2;
1762
+ } else {
1763
+ currentSyllable = word[i - 1] + currentSyllable;
1764
+ i--;
1765
+ }
1644
1766
  }
1645
1767
  }
1646
1768
  result.unshift(currentSyllable);
@@ -1670,6 +1792,292 @@ yDFx8r7i9vIJU5HS3moZLkYWAOilMaV9N56A9Bgb6dNcHkvg3NoaYA==
1670
1792
  const hasFront = frontVowels.test(lower);
1671
1793
  return !(hasBack && hasFront);
1672
1794
  }
1795
+ /**
1796
+ * Checks if a word follows Turkish Minor Vowel Harmony (Küçük Ünlü Uyumu / Labial Harmony).
1797
+ * Rules:
1798
+ * 1. After an unrounded vowel (a, e, ı, i), only unrounded vowels (a, e, ı, i) can follow.
1799
+ * 2. After a rounded vowel (o, ö, u, ü), either an unrounded wide (a, e) or rounded narrow (u, ü) vowel can follow.
1800
+ * Single-syllable words and words with <=1 vowel are considered compliant by convention.
1801
+ */
1802
+ static checkLabialHarmony(word) {
1803
+ const lower = word.toLocaleLowerCase("tr-TR");
1804
+ const vowels = lower.split("").filter((ch) => "ae\u0131io\xF6u\xFC".includes(ch));
1805
+ if (vowels.length <= 1)
1806
+ return true;
1807
+ for (let i = 0; i < vowels.length - 1; i++) {
1808
+ const v1 = vowels[i];
1809
+ const v2 = vowels[i + 1];
1810
+ if ("ae\u0131i".includes(v1)) {
1811
+ if (!"ae\u0131i".includes(v2))
1812
+ return false;
1813
+ } else if ("o\xF6u\xFC".includes(v1)) {
1814
+ if (!"aeu\xFC".includes(v2))
1815
+ return false;
1816
+ }
1817
+ }
1818
+ return true;
1819
+ }
1820
+ /**
1821
+ * Searches TDK headwords using a wildcard / pattern string.
1822
+ * Wildcards:
1823
+ * '_' or '?' matches any single character
1824
+ * '*' matches zero or more characters
1825
+ * Example: "k_l_m" matches "kalem", "kelam", "kilim".
1826
+ * Runs in-memory against TDK's 81k headword list.
1827
+ */
1828
+ static async patternSearch(pattern, options) {
1829
+ if (!pattern || pattern.trim() === "")
1830
+ return [];
1831
+ await this.ensureAutocompleteLoaded();
1832
+ const cleanPattern = pattern.trim().toLocaleLowerCase("tr-TR");
1833
+ const escaped = cleanPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/[_?]/g, "[\\p{L}]").replace(/\*/g, "[\\p{L}]*");
1834
+ const regex = new RegExp(`^${escaped}$`, "u");
1835
+ const max = options?.maxResults ?? 50;
1836
+ const matches = [];
1837
+ for (const headword of this.autocompleteCache) {
1838
+ const lower = headword.toLocaleLowerCase("tr-TR");
1839
+ if (regex.test(lower)) {
1840
+ matches.push(headword);
1841
+ if (matches.length >= max)
1842
+ break;
1843
+ }
1844
+ }
1845
+ return matches;
1846
+ }
1847
+ /**
1848
+ * Finds headwords in TDK that can be formed from the given letters (anagrams).
1849
+ * If exact-length anagrams exist, they are returned.
1850
+ * If none exist (or exactLength is false), valid sub-anagrams (words using a subset of the letters,
1851
+ * minimum 3 letters) are returned, sorted by length descending.
1852
+ */
1853
+ static async findAnagrams(letters, options) {
1854
+ if (!letters || letters.trim() === "")
1855
+ return [];
1856
+ await this.ensureAutocompleteLoaded();
1857
+ const clean = letters.trim().toLocaleLowerCase("tr-TR").replace(/[^a-zçğıöşüâîû]/gi, "");
1858
+ if (clean.length === 0)
1859
+ return [];
1860
+ const forceExact = options?.exactLength === true;
1861
+ const max = options?.maxResults ?? 50;
1862
+ const getFrequency = (str) => {
1863
+ const freq = {};
1864
+ for (const ch of str) {
1865
+ freq[ch] = (freq[ch] || 0) + 1;
1866
+ }
1867
+ return freq;
1868
+ };
1869
+ const targetFreq = getFrequency(clean);
1870
+ const exactMatches = [];
1871
+ const subMatches = [];
1872
+ for (const headword of this.autocompleteCache) {
1873
+ const lower = headword.toLocaleLowerCase("tr-TR");
1874
+ if (lower.includes(" ") || lower.includes("-"))
1875
+ continue;
1876
+ if (lower.length > clean.length || lower.length < 3)
1877
+ continue;
1878
+ const wordFreq = getFrequency(lower);
1879
+ let isValid = true;
1880
+ for (const [ch, count] of Object.entries(wordFreq)) {
1881
+ if (!targetFreq[ch] || targetFreq[ch] < count) {
1882
+ isValid = false;
1883
+ break;
1884
+ }
1885
+ }
1886
+ if (isValid && lower !== clean) {
1887
+ if (lower.length === clean.length) {
1888
+ exactMatches.push(headword);
1889
+ } else {
1890
+ subMatches.push(headword);
1891
+ }
1892
+ }
1893
+ }
1894
+ if (exactMatches.length > 0 || forceExact) {
1895
+ return exactMatches.slice(0, max);
1896
+ }
1897
+ subMatches.sort((a, b) => b.length - a.length || a.localeCompare(b, "tr-TR"));
1898
+ return subMatches.slice(0, max);
1899
+ }
1900
+ /**
1901
+ * Finds words in TDK that rhyme with the given word (sharing the same ending suffix/letters).
1902
+ * @param word The target word
1903
+ * @param options.minLetters Minimum number of ending characters that must match (default: 3)
1904
+ * @param options.maxResults Maximum number of rhyme results to return (default: 50)
1905
+ */
1906
+ static async findRhymes(word, options) {
1907
+ if (!word || word.trim() === "")
1908
+ return [];
1909
+ await this.ensureAutocompleteLoaded();
1910
+ const clean = word.trim().toLocaleLowerCase("tr-TR");
1911
+ const minLetters = Math.min(options?.minLetters ?? 3, clean.length);
1912
+ const max = options?.maxResults ?? 50;
1913
+ const suffix = clean.slice(-minLetters);
1914
+ const results = [];
1915
+ for (const headword of this.autocompleteCache) {
1916
+ const lower = headword.toLocaleLowerCase("tr-TR");
1917
+ if (lower !== clean && lower.endsWith(suffix) && !lower.includes(" ")) {
1918
+ results.push(headword);
1919
+ if (results.length >= max)
1920
+ break;
1921
+ }
1922
+ }
1923
+ return results;
1924
+ }
1925
+ /**
1926
+ * Performs comprehensive spelling, grammar, and syntax proofreading on a Turkish text.
1927
+ * Detects:
1928
+ * 1. Conjunction 'da/de' erroneously joined to verbs or words (e.g. "gitsende" -> "gitsen de")
1929
+ * 2. Conjunction 'ki' erroneously joined to verbs (e.g. "gördümki" -> "gördüm ki"), respecting SOMBAHÇEMİ exceptions
1930
+ * 3. Question particle 'mi/mı/mu/mü' erroneously joined to words (e.g. "geldimi" -> "geldi mi")
1931
+ * 4. Misspelled words with dictionary suggestions (via edit-distance & morphology)
1932
+ */
1933
+ static async proofread(text) {
1934
+ if (!text || text.trim() === "") {
1935
+ return { text: text || "", issues: [], isCorrect: true };
1936
+ }
1937
+ await this.ensureAutocompleteLoaded();
1938
+ const issues = [];
1939
+ const SOMBAHCEMI = /* @__PURE__ */ new Set([
1940
+ "sanki",
1941
+ "oysaki",
1942
+ "mademki",
1943
+ "belki",
1944
+ "halbuki",
1945
+ "\xE7\xFCnk\xFC",
1946
+ "me\u011Ferki",
1947
+ "illaki"
1948
+ ]);
1949
+ const tokenRegex = /[\p{L}0-9'’]+/gu;
1950
+ let match;
1951
+ while ((match = tokenRegex.exec(text)) !== null) {
1952
+ const rawWord = match[0];
1953
+ const startIndex = match.index;
1954
+ const endIndex = startIndex + rawWord.length;
1955
+ const lower = rawWord.toLocaleLowerCase("tr-TR");
1956
+ if (/^\d+$/.test(lower))
1957
+ continue;
1958
+ let flagged = false;
1959
+ 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)?)$/);
1960
+ if (questionMatch) {
1961
+ const base = questionMatch[1];
1962
+ const particle = questionMatch[2];
1963
+ if (base.length >= 2 && (await this.isHeadword(base) || await this.findRoot(base) !== null)) {
1964
+ if (!await this.isHeadword(lower)) {
1965
+ issues.push({
1966
+ type: "question_particle",
1967
+ word: rawWord,
1968
+ startIndex,
1969
+ endIndex,
1970
+ suggestion: `${base} ${particle}`,
1971
+ message: `'${particle}' soru eki kendinden \xF6nceki kelimeden ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
1972
+ });
1973
+ flagged = true;
1974
+ }
1975
+ }
1976
+ }
1977
+ 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;
1978
+ if (!flagged && lower.endsWith("ki") && lower.length > 3) {
1979
+ const base = lower.slice(0, -2);
1980
+ if (!SOMBAHCEMI.has(lower)) {
1981
+ if (!await this.isHeadword(lower)) {
1982
+ const root = await this.findRoot(base);
1983
+ const isVerb = root && (root.endsWith("mek") || root.endsWith("mak")) || base === "demek" || base === "kald\u0131" || base === "yeter" || base === "bilmem" || VERB_CONJUGATION_REGEX.test(base);
1984
+ if (isVerb) {
1985
+ issues.push({
1986
+ type: "conjunction_ki",
1987
+ word: rawWord,
1988
+ startIndex,
1989
+ endIndex,
1990
+ suggestion: `${base} ki`,
1991
+ message: `'ki' ba\u011Flac\u0131 ayr\u0131 yaz\u0131lmal\u0131d\u0131r.`
1992
+ });
1993
+ flagged = true;
1994
+ }
1995
+ }
1996
+ }
1997
+ }
1998
+ if (!flagged && (lower.endsWith("de") || lower.endsWith("da") || lower.endsWith("te") || lower.endsWith("ta")) && lower.length > 3) {
1999
+ const base = lower.slice(0, -2);
2000
+ const ending = lower.slice(-2);
2001
+ if (!await this.isHeadword(lower)) {
2002
+ const root = await this.findRoot(base);
2003
+ const isVerb = root && (root.endsWith("mek") || root.endsWith("mak")) || VERB_CONJUGATION_REGEX.test(base);
2004
+ if (isVerb) {
2005
+ const correctEnding = ending.startsWith("t") ? ending === "te" ? "de" : "da" : ending;
2006
+ issues.push({
2007
+ type: "conjunction_da",
2008
+ word: rawWord,
2009
+ startIndex,
2010
+ endIndex,
2011
+ suggestion: `${base} ${correctEnding}`,
2012
+ message: `'da/de' ba\u011Flac\u0131 fiillerden sonra her zaman ayr\u0131 yaz\u0131l\u0131r (ba\u011Fla\xE7 olan da/de sertle\u015Fmez).`
2013
+ });
2014
+ flagged = true;
2015
+ }
2016
+ }
2017
+ }
2018
+ if (!flagged) {
2019
+ const check = await this.checkSpelling(rawWord);
2020
+ if (!check.isCorrect) {
2021
+ issues.push({
2022
+ type: "spelling",
2023
+ word: rawWord,
2024
+ startIndex,
2025
+ endIndex,
2026
+ suggestion: check.suggestion,
2027
+ message: check.suggestion ? `'${rawWord}' yanl\u0131\u015F yaz\u0131lm\u0131\u015F olabilir. \xD6neri: '${check.suggestion}'` : `'${rawWord}' s\xF6zl\xFCkte bulunamad\u0131.`
2028
+ });
2029
+ }
2030
+ }
2031
+ }
2032
+ return {
2033
+ text,
2034
+ issues,
2035
+ isCorrect: issues.length === 0
2036
+ };
2037
+ }
2038
+ };
2039
+ var TDKClient = class {
2040
+ constructor(config) {
2041
+ if (config) {
2042
+ TDK.configure(config);
2043
+ }
2044
+ }
2045
+ getWord(word) {
2046
+ return TDK.getWord(word);
2047
+ }
2048
+ getMeanings(word) {
2049
+ return TDK.getMeanings(word);
2050
+ }
2051
+ checkSpelling(word) {
2052
+ return TDK.checkSpelling(word);
2053
+ }
2054
+ findRoot(word) {
2055
+ return TDK.findRoot(word);
2056
+ }
2057
+ stem(word) {
2058
+ return TDK.stem(word);
2059
+ }
2060
+ proofread(text) {
2061
+ return TDK.proofread(text);
2062
+ }
2063
+ patternSearch(pattern, options) {
2064
+ return TDK.patternSearch(pattern, options);
2065
+ }
2066
+ findAnagrams(letters, options) {
2067
+ return TDK.findAnagrams(letters, options);
2068
+ }
2069
+ findRhymes(word, options) {
2070
+ return TDK.findRhymes(word, options);
2071
+ }
2072
+ syllabicate(word) {
2073
+ return TDK.syllabicate(word);
2074
+ }
2075
+ checkVowelHarmony(word) {
2076
+ return TDK.checkVowelHarmony(word);
2077
+ }
2078
+ checkLabialHarmony(word) {
2079
+ return TDK.checkLabialHarmony(word);
2080
+ }
1673
2081
  };
1674
2082
 
1675
2083
  export {
@@ -1681,7 +2089,10 @@ export {
1681
2089
  TURKISH_SUFFIXES,
1682
2090
  restoreConsonantSoftening,
1683
2091
  restoreVowelDrop,
2092
+ restoreGemination,
2093
+ restoreVowelNarrowing,
1684
2094
  restoreInfinitive,
1685
2095
  getStemCandidates,
1686
- TDK
2096
+ TDK,
2097
+ TDKClient
1687
2098
  };