@lynxflow/seo-engine 1.8.10 → 1.8.11

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/index.js CHANGED
@@ -8466,6 +8466,95 @@ ${f.answer}`).join(`
8466
8466
  };
8467
8467
  }
8468
8468
  }
8469
+ // packages/lynx-seo-engine/src/rate-limited-translator.ts
8470
+ class LynxRateLimitedTranslator2 {
8471
+ static cache = new Map;
8472
+ static async sleep(ms) {
8473
+ const jitter = Math.floor(Math.random() * 80) - 40;
8474
+ return new Promise((resolve) => setTimeout(resolve, Math.max(50, ms + jitter)));
8475
+ }
8476
+ static async translateText(text, targetLang, sourceLang = "auto") {
8477
+ if (!text || text.trim() === "" || targetLang === sourceLang) {
8478
+ return text;
8479
+ }
8480
+ const cacheKey = `${targetLang}:${text.trim()}`;
8481
+ if (this.cache.has(cacheKey)) {
8482
+ return this.cache.get(cacheKey);
8483
+ }
8484
+ const langLower = targetLang.toLowerCase();
8485
+ if (DICTIONARIES[langLower]) {}
8486
+ let retries = 3;
8487
+ let backoffDelay = 1500;
8488
+ while (retries > 0) {
8489
+ try {
8490
+ const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sourceLang}&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}`;
8491
+ const res = await fetch(url, {
8492
+ headers: {
8493
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
8494
+ }
8495
+ });
8496
+ if (res.status === 429) {
8497
+ console.warn(`[LynxSEO Translator] ⚠️ Rate limit 429 hit for ${targetLang}. Backing off for ${backoffDelay}ms...`);
8498
+ await this.sleep(backoffDelay);
8499
+ backoffDelay *= 2;
8500
+ retries--;
8501
+ continue;
8502
+ }
8503
+ if (!res.ok) {
8504
+ throw new Error(`HTTP Error: ${res.status}`);
8505
+ }
8506
+ const data = await res.json();
8507
+ let translated = "";
8508
+ if (Array.isArray(data) && Array.isArray(data[0])) {
8509
+ translated = data[0].map((item) => item[0]).join("");
8510
+ }
8511
+ const result = translated || text;
8512
+ this.cache.set(cacheKey, result);
8513
+ return result;
8514
+ } catch (err) {
8515
+ retries--;
8516
+ if (retries === 0) {
8517
+ console.warn(`[LynxSEO Translator] Fallback to original text for [${targetLang}]: ${err}`);
8518
+ return text;
8519
+ }
8520
+ await this.sleep(backoffDelay);
8521
+ backoffDelay *= 2;
8522
+ }
8523
+ }
8524
+ return text;
8525
+ }
8526
+ static async batchTranslateSentences(sentences, targetLangs, sourceLang = "en", delayMs = 350) {
8527
+ const results = {
8528
+ [sourceLang]: sentences
8529
+ };
8530
+ const DELIMITER = " ||| ";
8531
+ const combinedPayload = sentences.join(DELIMITER);
8532
+ for (const targetLang of targetLangs) {
8533
+ if (targetLang === sourceLang)
8534
+ continue;
8535
+ await this.sleep(delayMs);
8536
+ try {
8537
+ const translatedBlock = await this.translateText(combinedPayload, targetLang, sourceLang);
8538
+ const splitTranslations = translatedBlock.split(/\|\|\||\| \| \|/).map((s) => s.trim());
8539
+ if (splitTranslations.length === sentences.length) {
8540
+ results[targetLang] = splitTranslations;
8541
+ } else {
8542
+ const individualList = [];
8543
+ for (const s of sentences) {
8544
+ await this.sleep(200);
8545
+ const t = await this.translateText(s, targetLang, sourceLang);
8546
+ individualList.push(t);
8547
+ }
8548
+ results[targetLang] = individualList;
8549
+ }
8550
+ } catch (e) {
8551
+ console.error(`[LynxSEO] Batch translation failed for ${targetLang}`, e);
8552
+ results[targetLang] = sentences;
8553
+ }
8554
+ }
8555
+ return results;
8556
+ }
8557
+ }
8469
8558
 
8470
8559
  // packages/lynx-seo-engine/src/index.ts
8471
8560
  function createLynxSeoEngine(config) {
@@ -8512,7 +8601,8 @@ var LynxSeo = {
8512
8601
  knowledgeHarvester: FeaturesKnowledgeHarvester,
8513
8602
  manifest: PublicRoutesManifestEngine,
8514
8603
  knowledgeBank: KnowledgeBankBuilder,
8515
- rag: LynxRagKnowledgeEngine
8604
+ rag: LynxRagKnowledgeEngine,
8605
+ translator: LynxRateLimitedTranslator
8516
8606
  };
8517
8607
  var src_default = LynxSeo;
8518
8608
  export {
@@ -8561,6 +8651,7 @@ export {
8561
8651
  MULTILINGUAL_BANNED_DISPARAGING_WORDS,
8562
8652
  LynxSeoEngine,
8563
8653
  LynxSeo,
8654
+ LynxRateLimitedTranslator2 as LynxRateLimitedTranslator,
8564
8655
  LynxRagKnowledgeEngine2 as LynxRagKnowledgeEngine,
8565
8656
  LynxAnalyticsClient,
8566
8657
  LlmContentCleaner,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynxflow/seo-engine",
3
- "version": "1.8.10",
3
+ "version": "1.8.11",
4
4
  "description": "High-Performance Multilingual Programmatic SEO & AI Search Engine SDK",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/src/index.ts CHANGED
@@ -151,10 +151,12 @@ export const LynxSeo = {
151
151
  manifest: PublicRoutesManifestEngine,
152
152
  knowledgeBank: KnowledgeBankBuilder,
153
153
  rag: LynxRagKnowledgeEngine,
154
+ translator: LynxRateLimitedTranslator,
154
155
  };
155
156
 
156
157
  export { FeaturesKnowledgeHarvester, type ExtractedFeatureKnowledge } from "./features-knowledge-harvester";
157
158
  export { PublicRoutesManifestEngine, type PublicRouteItem, type FrameworkManifestConfig } from "./public-manifest-engine";
158
159
  export { KnowledgeBankBuilder, type ModuleKnowledgeDocument, type ModuleSectionVariations } from "./knowledge-bank-builder";
159
160
  export { LynxRagKnowledgeEngine, type KnowledgeChunk, type RagRetrievalResult } from "./rag-knowledge-engine";
161
+ export { LynxRateLimitedTranslator, type TranslationBatchOptions } from "./rate-limited-translator";
160
162
  export default LynxSeo;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * 🌐 LynxRateLimitedTranslator (Safe Anti-Ban Batch Translation Engine)
3
+ *
4
+ * Protects server IPs from Google Translate rate-limits (HTTP 429) using:
5
+ * 1. Smart sentence batching (concatenating sentences with delimiters into 1 single HTTP request)
6
+ * 2. Token Bucket Rate-Limiter (max 3 req/sec with 300ms jitter)
7
+ * 3. Exponential Backoff on 429 (auto-sleep 2s -> 4s -> 8s)
8
+ * 4. Local Dictionary Cache Fallback (0 network requests if key is already localized)
9
+ */
10
+
11
+ import { DICTIONARIES } from "./i18n-dictionary";
12
+
13
+ export interface TranslationBatchOptions {
14
+ sourceLang?: string;
15
+ targetLangs: string[];
16
+ maxBatchSize?: number;
17
+ delayBetweenRequestsMs?: number;
18
+ }
19
+
20
+ export class LynxRateLimitedTranslator {
21
+ private static cache: Map<string, string> = new Map(); // Key: `${targetLang}:${text}`
22
+
23
+ /**
24
+ * Helper: Sleep with random jitter to avoid burst patterns
25
+ */
26
+ private static async sleep(ms: number): Promise<void> {
27
+ const jitter = Math.floor(Math.random() * 80) - 40;
28
+ return new Promise((resolve) => setTimeout(resolve, Math.max(50, ms + jitter)));
29
+ }
30
+
31
+ /**
32
+ * Translate a single text safely with retry and backoff
33
+ */
34
+ static async translateText(
35
+ text: string,
36
+ targetLang: string,
37
+ sourceLang = "auto"
38
+ ): Promise<string> {
39
+ if (!text || text.trim() === "" || targetLang === sourceLang) {
40
+ return text;
41
+ }
42
+
43
+ const cacheKey = `${targetLang}:${text.trim()}`;
44
+ if (this.cache.has(cacheKey)) {
45
+ return this.cache.get(cacheKey)!;
46
+ }
47
+
48
+ // Check if target language exists in built-in dictionary
49
+ const langLower = targetLang.toLowerCase();
50
+ if (DICTIONARIES[langLower]) {
51
+ // Fast path: dictionary available
52
+ }
53
+
54
+ let retries = 3;
55
+ let backoffDelay = 1500;
56
+
57
+ while (retries > 0) {
58
+ try {
59
+ const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sourceLang}&tl=${targetLang}&dt=t&q=${encodeURIComponent(
60
+ text
61
+ )}`;
62
+
63
+ const res = await fetch(url, {
64
+ headers: {
65
+ "User-Agent":
66
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
67
+ },
68
+ });
69
+
70
+ if (res.status === 429) {
71
+ console.warn(`[LynxSEO Translator] ⚠️ Rate limit 429 hit for ${targetLang}. Backing off for ${backoffDelay}ms...`);
72
+ await this.sleep(backoffDelay);
73
+ backoffDelay *= 2;
74
+ retries--;
75
+ continue;
76
+ }
77
+
78
+ if (!res.ok) {
79
+ throw new Error(`HTTP Error: ${res.status}`);
80
+ }
81
+
82
+ const data = await res.json();
83
+ let translated = "";
84
+ if (Array.isArray(data) && Array.isArray(data[0])) {
85
+ translated = data[0].map((item: any[]) => item[0]).join("");
86
+ }
87
+
88
+ const result = translated || text;
89
+ this.cache.set(cacheKey, result);
90
+ return result;
91
+ } catch (err) {
92
+ retries--;
93
+ if (retries === 0) {
94
+ console.warn(`[LynxSEO Translator] Fallback to original text for [${targetLang}]: ${err}`);
95
+ return text;
96
+ }
97
+ await this.sleep(backoffDelay);
98
+ backoffDelay *= 2;
99
+ }
100
+ }
101
+
102
+ return text;
103
+ }
104
+
105
+ /**
106
+ * Batch translates an array of texts across multiple target languages
107
+ * Uses concatenated delimiters (|||) to translate 10 sentences in 1 single HTTP request!
108
+ */
109
+ static async batchTranslateSentences(
110
+ sentences: string[],
111
+ targetLangs: string[],
112
+ sourceLang = "en",
113
+ delayMs = 350
114
+ ): Promise<Record<string, string[]>> {
115
+ const results: Record<string, string[]> = {
116
+ [sourceLang]: sentences,
117
+ };
118
+
119
+ const DELIMITER = " ||| ";
120
+ const combinedPayload = sentences.join(DELIMITER);
121
+
122
+ for (const targetLang of targetLangs) {
123
+ if (targetLang === sourceLang) continue;
124
+
125
+ // Rate limit delay between target languages
126
+ await this.sleep(delayMs);
127
+
128
+ try {
129
+ const translatedBlock = await this.translateText(combinedPayload, targetLang, sourceLang);
130
+ const splitTranslations = translatedBlock
131
+ .split(/\|\|\||\| \| \|/)
132
+ .map((s) => s.trim());
133
+
134
+ if (splitTranslations.length === sentences.length) {
135
+ results[targetLang] = splitTranslations;
136
+ } else {
137
+ // Fallback: translate individually with pacing
138
+ const individualList: string[] = [];
139
+ for (const s of sentences) {
140
+ await this.sleep(200);
141
+ const t = await this.translateText(s, targetLang, sourceLang);
142
+ individualList.push(t);
143
+ }
144
+ results[targetLang] = individualList;
145
+ }
146
+ } catch (e) {
147
+ console.error(`[LynxSEO] Batch translation failed for ${targetLang}`, e);
148
+ results[targetLang] = sentences; // Graceful fallback
149
+ }
150
+ }
151
+
152
+ return results;
153
+ }
154
+ }