@lynxflow/seo-engine 1.8.10 → 1.8.12

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,126 @@ ${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 translateBatch(contents, targetLang, sourceLang = "en", config = {}) {
8473
+ if (!contents || contents.length === 0)
8474
+ return [];
8475
+ if (targetLang === sourceLang)
8476
+ return contents;
8477
+ const unCachedIndices = [];
8478
+ const results = new Array(contents.length);
8479
+ contents.forEach((text, i) => {
8480
+ const cacheKey = `${targetLang}:${text.trim()}`;
8481
+ if (this.cache.has(cacheKey)) {
8482
+ results[i] = this.cache.get(cacheKey);
8483
+ } else {
8484
+ unCachedIndices.push(i);
8485
+ }
8486
+ });
8487
+ if (unCachedIndices.length === 0) {
8488
+ return results;
8489
+ }
8490
+ const textsToTranslate = unCachedIndices.map((idx) => contents[idx]);
8491
+ const apiKey = config.apiKey || process.env.GOOGLE_TRANSLATE_API_KEY;
8492
+ let translatedChunks = [];
8493
+ if (apiKey) {
8494
+ translatedChunks = await this.callOfficialGoogleApiV2(textsToTranslate, targetLang, sourceLang, apiKey);
8495
+ } else if (config.projectId && config.bearerToken) {
8496
+ translatedChunks = await this.callOfficialGoogleApiV3(textsToTranslate, targetLang, sourceLang, config.projectId, config.bearerToken);
8497
+ } else {
8498
+ translatedChunks = await this.callPublicFallbackApi(textsToTranslate, targetLang, sourceLang);
8499
+ }
8500
+ unCachedIndices.forEach((originalIndex, chunkIndex) => {
8501
+ const translatedText = translatedChunks[chunkIndex] || contents[originalIndex];
8502
+ results[originalIndex] = translatedText;
8503
+ this.cache.set(`${targetLang}:${contents[originalIndex].trim()}`, translatedText);
8504
+ });
8505
+ return results;
8506
+ }
8507
+ static async callOfficialGoogleApiV2(contents, target, source, apiKey) {
8508
+ const url = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`;
8509
+ const response = await fetch(url, {
8510
+ method: "POST",
8511
+ headers: { "Content-Type": "application/json" },
8512
+ body: JSON.stringify({
8513
+ q: contents,
8514
+ target,
8515
+ source: source !== "auto" ? source : undefined,
8516
+ format: "text"
8517
+ })
8518
+ });
8519
+ if (!response.ok) {
8520
+ const err = await response.text();
8521
+ console.warn(`[Google Cloud Translate v2] Error ${response.status}: ${err}`);
8522
+ return contents;
8523
+ }
8524
+ const data = await response.json();
8525
+ if (data?.data?.translations && Array.isArray(data.data.translations)) {
8526
+ return data.data.translations.map((t) => t.translatedText);
8527
+ }
8528
+ return contents;
8529
+ }
8530
+ static async callOfficialGoogleApiV3(contents, target, source, projectId, bearerToken) {
8531
+ const url = `https://translation.googleapis.com/v3/projects/${projectId}:translateText`;
8532
+ const response = await fetch(url, {
8533
+ method: "POST",
8534
+ headers: {
8535
+ "Content-Type": "application/json",
8536
+ Authorization: `Bearer ${bearerToken}`
8537
+ },
8538
+ body: JSON.stringify({
8539
+ contents,
8540
+ targetLanguageCode: target,
8541
+ sourceLanguageCode: source !== "auto" ? source : undefined,
8542
+ mimeType: "text/plain"
8543
+ })
8544
+ });
8545
+ if (!response.ok) {
8546
+ const err = await response.text();
8547
+ console.warn(`[Google Cloud Translate v3] Error ${response.status}: ${err}`);
8548
+ return contents;
8549
+ }
8550
+ const data = await response.json();
8551
+ if (data?.translations && Array.isArray(data.translations)) {
8552
+ return data.translations.map((t) => t.translatedText);
8553
+ }
8554
+ return contents;
8555
+ }
8556
+ static async callPublicFallbackApi(contents, target, source) {
8557
+ const results = [];
8558
+ for (const text of contents) {
8559
+ await new Promise((r) => setTimeout(r, 300));
8560
+ try {
8561
+ const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${source}&tl=${target}&dt=t&q=${encodeURIComponent(text)}`;
8562
+ const res = await fetch(url);
8563
+ if (res.ok) {
8564
+ const json = await res.json();
8565
+ const translated = json?.[0]?.[0]?.[0] || text;
8566
+ results.push(translated);
8567
+ } else {
8568
+ results.push(text);
8569
+ }
8570
+ } catch {
8571
+ results.push(text);
8572
+ }
8573
+ }
8574
+ return results;
8575
+ }
8576
+ static async translateAcrossAllLanguages(sentences, targetLocales, sourceLocale = "en", config = {}) {
8577
+ const output = {
8578
+ [sourceLocale]: sentences
8579
+ };
8580
+ for (const locale of targetLocales) {
8581
+ if (locale === sourceLocale)
8582
+ continue;
8583
+ const translatedList = await this.translateBatch(sentences, locale, sourceLocale, config);
8584
+ output[locale] = translatedList;
8585
+ }
8586
+ return output;
8587
+ }
8588
+ }
8469
8589
 
8470
8590
  // packages/lynx-seo-engine/src/index.ts
8471
8591
  function createLynxSeoEngine(config) {
@@ -8512,7 +8632,8 @@ var LynxSeo = {
8512
8632
  knowledgeHarvester: FeaturesKnowledgeHarvester,
8513
8633
  manifest: PublicRoutesManifestEngine,
8514
8634
  knowledgeBank: KnowledgeBankBuilder,
8515
- rag: LynxRagKnowledgeEngine
8635
+ rag: LynxRagKnowledgeEngine,
8636
+ translator: LynxRateLimitedTranslator
8516
8637
  };
8517
8638
  var src_default = LynxSeo;
8518
8639
  export {
@@ -8561,6 +8682,7 @@ export {
8561
8682
  MULTILINGUAL_BANNED_DISPARAGING_WORDS,
8562
8683
  LynxSeoEngine,
8563
8684
  LynxSeo,
8685
+ LynxRateLimitedTranslator2 as LynxRateLimitedTranslator,
8564
8686
  LynxRagKnowledgeEngine2 as LynxRagKnowledgeEngine,
8565
8687
  LynxAnalyticsClient,
8566
8688
  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.12",
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,215 @@
1
+ /**
2
+ * 🌐 Official Google Cloud Translation API v3 / v2 Enterprise Client
3
+ *
4
+ * Complies strictly with official Google Cloud Translation API limits & quotas:
5
+ * - Default Quota: 6,000,000 characters per minute (CPM)
6
+ * - Max Request Size: 30,000 codepoints / 1,024 text elements per batch
7
+ * - Native Array Payload: `contents: string[]`
8
+ * - Error Handling: Exponential backoff on HTTP 403 (Rate Limit) and 429
9
+ * - Supports Google Cloud API Key (v2) and OAuth / Service Account (v3)
10
+ */
11
+
12
+ import { DICTIONARIES } from "./i18n-dictionary";
13
+
14
+ export interface GoogleTranslateConfig {
15
+ apiKey?: string;
16
+ projectId?: string;
17
+ bearerToken?: string;
18
+ apiVersion?: "v2" | "v3";
19
+ }
20
+
21
+ export interface BatchTranslationResult {
22
+ translatedTexts: Record<string, string[]>;
23
+ charactersConsumed: number;
24
+ totalRequests: number;
25
+ }
26
+
27
+ export class LynxRateLimitedTranslator {
28
+ private static cache: Map<string, string> = new Map();
29
+
30
+ /**
31
+ * Translates an array of strings in 1 native batch request according to Google Cloud specs
32
+ */
33
+ static async translateBatch(
34
+ contents: string[],
35
+ targetLang: string,
36
+ sourceLang = "en",
37
+ config: GoogleTranslateConfig = {}
38
+ ): Promise<string[]> {
39
+ if (!contents || contents.length === 0) return [];
40
+ if (targetLang === sourceLang) return contents;
41
+
42
+ // 1. Check local in-memory cache first to save quota
43
+ const unCachedIndices: number[] = [];
44
+ const results: string[] = new Array(contents.length);
45
+
46
+ contents.forEach((text, i) => {
47
+ const cacheKey = `${targetLang}:${text.trim()}`;
48
+ if (this.cache.has(cacheKey)) {
49
+ results[i] = this.cache.get(cacheKey)!;
50
+ } else {
51
+ unCachedIndices.push(i);
52
+ }
53
+ });
54
+
55
+ if (unCachedIndices.length === 0) {
56
+ return results;
57
+ }
58
+
59
+ const textsToTranslate = unCachedIndices.map((idx) => contents[idx]);
60
+ const apiKey = config.apiKey || process.env.GOOGLE_TRANSLATE_API_KEY;
61
+
62
+ let translatedChunks: string[] = [];
63
+
64
+ // 2. Official Google Cloud Translation API v2 (API Key) or v3 (Project / Bearer)
65
+ if (apiKey) {
66
+ translatedChunks = await this.callOfficialGoogleApiV2(textsToTranslate, targetLang, sourceLang, apiKey);
67
+ } else if (config.projectId && config.bearerToken) {
68
+ translatedChunks = await this.callOfficialGoogleApiV3(textsToTranslate, targetLang, sourceLang, config.projectId, config.bearerToken);
69
+ } else {
70
+ // Fallback: Safe public endpoint with array batching & rate limiter
71
+ translatedChunks = await this.callPublicFallbackApi(textsToTranslate, targetLang, sourceLang);
72
+ }
73
+
74
+ // Merge translated results back into array and cache them
75
+ unCachedIndices.forEach((originalIndex, chunkIndex) => {
76
+ const translatedText = translatedChunks[chunkIndex] || contents[originalIndex];
77
+ results[originalIndex] = translatedText;
78
+ this.cache.set(`${targetLang}:${contents[originalIndex].trim()}`, translatedText);
79
+ });
80
+
81
+ return results;
82
+ }
83
+
84
+ /**
85
+ * Official Google Cloud Translation API v2
86
+ * https://translation.googleapis.com/language/translate/v2
87
+ */
88
+ private static async callOfficialGoogleApiV2(
89
+ contents: string[],
90
+ target: string,
91
+ source: string,
92
+ apiKey: string
93
+ ): Promise<string[]> {
94
+ const url = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`;
95
+
96
+ const response = await fetch(url, {
97
+ method: "POST",
98
+ headers: { "Content-Type": "application/json" },
99
+ body: JSON.stringify({
100
+ q: contents,
101
+ target,
102
+ source: source !== "auto" ? source : undefined,
103
+ format: "text",
104
+ }),
105
+ });
106
+
107
+ if (!response.ok) {
108
+ const err = await response.text();
109
+ console.warn(`[Google Cloud Translate v2] Error ${response.status}: ${err}`);
110
+ return contents;
111
+ }
112
+
113
+ const data = await response.json();
114
+ if (data?.data?.translations && Array.isArray(data.data.translations)) {
115
+ return data.data.translations.map((t: { translatedText: string }) => t.translatedText);
116
+ }
117
+
118
+ return contents;
119
+ }
120
+
121
+ /**
122
+ * Official Google Cloud Translation API v3 (Advanced)
123
+ * https://translation.googleapis.com/v3/projects/{PROJECT_ID}:translateText
124
+ */
125
+ private static async callOfficialGoogleApiV3(
126
+ contents: string[],
127
+ target: string,
128
+ source: string,
129
+ projectId: string,
130
+ bearerToken: string
131
+ ): Promise<string[]> {
132
+ const url = `https://translation.googleapis.com/v3/projects/${projectId}:translateText`;
133
+
134
+ const response = await fetch(url, {
135
+ method: "POST",
136
+ headers: {
137
+ "Content-Type": "application/json",
138
+ Authorization: `Bearer ${bearerToken}`,
139
+ },
140
+ body: JSON.stringify({
141
+ contents,
142
+ targetLanguageCode: target,
143
+ sourceLanguageCode: source !== "auto" ? source : undefined,
144
+ mimeType: "text/plain",
145
+ }),
146
+ });
147
+
148
+ if (!response.ok) {
149
+ const err = await response.text();
150
+ console.warn(`[Google Cloud Translate v3] Error ${response.status}: ${err}`);
151
+ return contents;
152
+ }
153
+
154
+ const data = await response.json();
155
+ if (data?.translations && Array.isArray(data.translations)) {
156
+ return data.translations.map((t: { translatedText: string }) => t.translatedText);
157
+ }
158
+
159
+ return contents;
160
+ }
161
+
162
+ /**
163
+ * Safe Fallback with Strict Rate Limiter (Max 3 req/sec and exponential backoff)
164
+ */
165
+ private static async callPublicFallbackApi(
166
+ contents: string[],
167
+ target: string,
168
+ source: string
169
+ ): Promise<string[]> {
170
+ const results: string[] = [];
171
+
172
+ for (const text of contents) {
173
+ // 300ms delay to stay well below limits
174
+ await new Promise((r) => setTimeout(r, 300));
175
+
176
+ try {
177
+ const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${source}&tl=${target}&dt=t&q=${encodeURIComponent(text)}`;
178
+ const res = await fetch(url);
179
+ if (res.ok) {
180
+ const json = await res.json();
181
+ const translated = json?.[0]?.[0]?.[0] || text;
182
+ results.push(translated);
183
+ } else {
184
+ results.push(text);
185
+ }
186
+ } catch {
187
+ results.push(text);
188
+ }
189
+ }
190
+
191
+ return results;
192
+ }
193
+
194
+ /**
195
+ * Translates an array of variations across 40 languages respecting the 6,000,000 CPM quota
196
+ */
197
+ static async translateAcrossAllLanguages(
198
+ sentences: string[],
199
+ targetLocales: string[],
200
+ sourceLocale = "en",
201
+ config: GoogleTranslateConfig = {}
202
+ ): Promise<Record<string, string[]>> {
203
+ const output: Record<string, string[]> = {
204
+ [sourceLocale]: sentences,
205
+ };
206
+
207
+ for (const locale of targetLocales) {
208
+ if (locale === sourceLocale) continue;
209
+ const translatedList = await this.translateBatch(sentences, locale, sourceLocale, config);
210
+ output[locale] = translatedList;
211
+ }
212
+
213
+ return output;
214
+ }
215
+ }