@lynxflow/seo-engine 1.0.0 → 1.2.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 +333 -82
- package/connectors/cloudflare-worker/worker.js +54 -26
- package/connectors/laravel/LynxSeoController.php +8 -8
- package/connectors/wordpress/lynxseo-connector.php +296 -53
- package/dist/ai-copilot-client.d.ts +36 -0
- package/dist/analytics-client.d.ts +53 -0
- package/dist/auth-key.d.ts +57 -0
- package/dist/backlinks-client.d.ts +31 -0
- package/dist/engine.d.ts +73 -0
- package/dist/i18n-dictionary.d.ts +30 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +1265 -169
- package/dist/index.mjs +1463 -0
- package/dist/indexnow-client.d.ts +25 -0
- package/dist/lago-token-meter.d.ts +36 -0
- package/dist/schema-builder.d.ts +27 -0
- package/dist/serp-client.d.ts +42 -0
- package/dist/site-auditor.d.ts +29 -0
- package/dist/src/ai-copilot-client.d.ts +36 -0
- package/dist/src/analytics-client.d.ts +53 -0
- package/dist/src/auth-key.d.ts +57 -0
- package/dist/src/backlinks-client.d.ts +31 -0
- package/dist/src/engine.d.ts +72 -0
- package/dist/src/i18n-dictionary.d.ts +30 -0
- package/dist/src/index.d.ts +42 -0
- package/dist/src/indexnow-client.d.ts +25 -0
- package/dist/src/lago-token-meter.d.ts +36 -0
- package/dist/src/schema-builder.d.ts +27 -0
- package/dist/src/serp-client.d.ts +42 -0
- package/dist/src/site-auditor.d.ts +29 -0
- package/dist/src/token-quota-manager.d.ts +37 -0
- package/dist/src/types.d.ts +145 -0
- package/dist/token-quota-manager.d.ts +37 -0
- package/dist/types.d.ts +162 -0
- package/lynxflow-seo-engine-1.2.0.tgz +0 -0
- package/package.json +1 -1
- package/src/ai-copilot-client.ts +84 -0
- package/src/analytics-client.ts +141 -0
- package/src/auth-key.ts +203 -0
- package/src/backlinks-client.ts +85 -0
- package/src/engine.ts +499 -85
- package/src/i18n-dictionary.ts +362 -0
- package/src/index.ts +18 -4
- package/src/indexnow-client.ts +94 -0
- package/src/lago-token-meter.ts +7 -2
- package/src/schema-builder.ts +87 -0
- package/src/serp-client.ts +89 -0
- package/src/site-auditor.ts +83 -0
- package/src/types.ts +148 -28
- package/tsconfig.json +14 -0
- package/lynxflow-seo-engine-1.0.0.tgz +0 -0
- package/src/licensing.ts +0 -138
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1463 @@
|
|
|
1
|
+
// src/token-quota-manager.ts
|
|
2
|
+
class TokenQuotaManager {
|
|
3
|
+
tier;
|
|
4
|
+
monthlyCreditBudget;
|
|
5
|
+
usedCredits = 0;
|
|
6
|
+
constructor(tier = "enterprise") {
|
|
7
|
+
this.tier = tier;
|
|
8
|
+
this.monthlyCreditBudget = this.resolveBudget(tier);
|
|
9
|
+
}
|
|
10
|
+
resolveBudget(tier) {
|
|
11
|
+
switch (tier) {
|
|
12
|
+
case "enterprise":
|
|
13
|
+
return 50000;
|
|
14
|
+
case "growth":
|
|
15
|
+
return 5000;
|
|
16
|
+
case "starter":
|
|
17
|
+
default:
|
|
18
|
+
return 500;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
consumeTokens(promptTokens, completionTokens, model, rawProviderCostUsd = 0.002) {
|
|
22
|
+
const totalTokens = promptTokens + completionTokens;
|
|
23
|
+
const costWithMarginUsd = rawProviderCostUsd * (totalTokens / 1000) * 1.2;
|
|
24
|
+
const creditsCharged = Math.max(1, Math.ceil(costWithMarginUsd * 100));
|
|
25
|
+
this.usedCredits += creditsCharged;
|
|
26
|
+
if (this.usedCredits > this.monthlyCreditBudget) {
|
|
27
|
+
console.warn(`⚠️ [LynxFlow Credit Quota] Monthly credit budget exceeded (${this.usedCredits} / ${this.monthlyCreditBudget} cr). Switching to zero-cost algorithmic mode.`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
promptTokens,
|
|
31
|
+
completionTokens,
|
|
32
|
+
totalTokens,
|
|
33
|
+
creditsCharged,
|
|
34
|
+
model,
|
|
35
|
+
timestamp: new Date().toISOString()
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
getStatus() {
|
|
39
|
+
return {
|
|
40
|
+
tier: this.tier,
|
|
41
|
+
monthlyCreditBudget: this.monthlyCreditBudget,
|
|
42
|
+
usedCredits: this.usedCredits,
|
|
43
|
+
remainingCredits: Math.max(0, this.monthlyCreditBudget - this.usedCredits),
|
|
44
|
+
isQuotaExceeded: this.usedCredits >= this.monthlyCreditBudget
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/auth-key.ts
|
|
50
|
+
class ApiKeyGuardian {
|
|
51
|
+
static domainRegistry = new Map;
|
|
52
|
+
static uniquePageRegistry = new Map;
|
|
53
|
+
static computeChecksum(payload, secret = "lynxflow_betterauth_secret_2026") {
|
|
54
|
+
const raw = `${payload}:${secret}`;
|
|
55
|
+
let hash = 0;
|
|
56
|
+
for (let i = 0;i < raw.length; i++) {
|
|
57
|
+
hash = (hash << 5) - hash + raw.charCodeAt(i);
|
|
58
|
+
hash |= 0;
|
|
59
|
+
}
|
|
60
|
+
return Math.abs(hash).toString(36).substring(0, 6);
|
|
61
|
+
}
|
|
62
|
+
static generateKey(tenantId, tier = "growth") {
|
|
63
|
+
const checksum = this.computeChecksum(`${tenantId}:${tier}`);
|
|
64
|
+
return `ba_key_${tier}_${tenantId}_${checksum}`;
|
|
65
|
+
}
|
|
66
|
+
static validate(apiKey, domain) {
|
|
67
|
+
if (!apiKey || typeof apiKey !== "string") {
|
|
68
|
+
return {
|
|
69
|
+
isValid: false,
|
|
70
|
+
tier: "starter",
|
|
71
|
+
maxPages: 100,
|
|
72
|
+
maxDomains: 1,
|
|
73
|
+
monthlyCreditBudget: 500,
|
|
74
|
+
tokenManager: new TokenQuotaManager("starter"),
|
|
75
|
+
errorMessage: "Missing API Key. Please set LYNXFLOW_API_KEY or pass apiKey in config."
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const cleanKey = apiKey.trim();
|
|
79
|
+
if (cleanKey.startsWith("ba_admin_") || cleanKey.startsWith("lynx_enterprise_") || cleanKey.startsWith("lynx_live_") || cleanKey.includes("_enterprise_")) {
|
|
80
|
+
return {
|
|
81
|
+
isValid: true,
|
|
82
|
+
tier: "enterprise",
|
|
83
|
+
tenantId: "enterprise_tenant",
|
|
84
|
+
maxPages: 5000000,
|
|
85
|
+
maxDomains: 9999,
|
|
86
|
+
monthlyCreditBudget: 50000,
|
|
87
|
+
tokenManager: new TokenQuotaManager("enterprise")
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (cleanKey.startsWith("ba_key_starter_") || cleanKey.startsWith("ba_test_") || cleanKey.startsWith("lynx_starter_") || cleanKey.includes("_starter_") || cleanKey.includes("_test_")) {
|
|
91
|
+
const parts2 = cleanKey.split("_");
|
|
92
|
+
const tenantId2 = parts2.length >= 4 ? parts2[3] : parts2[2] || "starter_tenant";
|
|
93
|
+
if (domain && !this.checkDomainAllowance(cleanKey, domain, 1)) {
|
|
94
|
+
return {
|
|
95
|
+
isValid: false,
|
|
96
|
+
tier: "starter",
|
|
97
|
+
tenantId: tenantId2,
|
|
98
|
+
maxPages: 50000,
|
|
99
|
+
maxDomains: 1,
|
|
100
|
+
monthlyCreditBudget: 1000,
|
|
101
|
+
tokenManager: new TokenQuotaManager("starter"),
|
|
102
|
+
errorMessage: `Single-Domain Lock: Starter tier is locked to 1 domain. Please upgrade to Growth or Enterprise for multi-domain support.`
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
isValid: true,
|
|
107
|
+
tier: "starter",
|
|
108
|
+
tenantId: tenantId2,
|
|
109
|
+
maxPages: 50000,
|
|
110
|
+
maxDomains: 1,
|
|
111
|
+
monthlyCreditBudget: 1000,
|
|
112
|
+
tokenManager: new TokenQuotaManager("starter")
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const parts = cleanKey.split("_");
|
|
116
|
+
const tenantId = parts.length >= 4 ? parts[3] : parts[2] || "growth_tenant";
|
|
117
|
+
if (domain && !this.checkDomainAllowance(cleanKey, domain, 3)) {
|
|
118
|
+
return {
|
|
119
|
+
isValid: false,
|
|
120
|
+
tier: "growth",
|
|
121
|
+
tenantId,
|
|
122
|
+
maxPages: 500000,
|
|
123
|
+
maxDomains: 3,
|
|
124
|
+
monthlyCreditBudget: 5000,
|
|
125
|
+
tokenManager: new TokenQuotaManager("growth"),
|
|
126
|
+
errorMessage: `Domain Limit Exceeded: Growth tier allows up to 3 domains. Please upgrade to Enterprise for unlimited domains.`
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
isValid: true,
|
|
131
|
+
tier: "growth",
|
|
132
|
+
tenantId,
|
|
133
|
+
maxPages: 500000,
|
|
134
|
+
maxDomains: 3,
|
|
135
|
+
monthlyCreditBudget: 5000,
|
|
136
|
+
tokenManager: new TokenQuotaManager("growth")
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
static checkDomainAllowance(apiKey, domain, maxDomains) {
|
|
140
|
+
const cleanDomain = domain.replace(/^https?:\/\//, "").replace(/\/.*$/, "").toLowerCase();
|
|
141
|
+
let domains = this.domainRegistry.get(apiKey);
|
|
142
|
+
if (!domains) {
|
|
143
|
+
domains = new Set;
|
|
144
|
+
this.domainRegistry.set(apiKey, domains);
|
|
145
|
+
}
|
|
146
|
+
if (domains.has(cleanDomain)) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
if (domains.size < maxDomains) {
|
|
150
|
+
domains.add(cleanDomain);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
static trackAndCheckUniquePage(apiKey, path, maxPages) {
|
|
156
|
+
const normalizedPath = path.toLowerCase().replace(/\/+$/, "") || "/";
|
|
157
|
+
let pages = this.uniquePageRegistry.get(apiKey);
|
|
158
|
+
if (!pages) {
|
|
159
|
+
pages = new Set;
|
|
160
|
+
this.uniquePageRegistry.set(apiKey, pages);
|
|
161
|
+
}
|
|
162
|
+
if (pages.has(normalizedPath)) {
|
|
163
|
+
return { allowed: true, uniqueCount: pages.size, isNewPage: false };
|
|
164
|
+
}
|
|
165
|
+
if (pages.size >= maxPages) {
|
|
166
|
+
return { allowed: false, uniqueCount: pages.size, isNewPage: true };
|
|
167
|
+
}
|
|
168
|
+
pages.add(normalizedPath);
|
|
169
|
+
return { allowed: true, uniqueCount: pages.size, isNewPage: true };
|
|
170
|
+
}
|
|
171
|
+
static getUniquePageCount(apiKey) {
|
|
172
|
+
return this.uniquePageRegistry.get(apiKey)?.size || 0;
|
|
173
|
+
}
|
|
174
|
+
static getUniquePageList(apiKey) {
|
|
175
|
+
const pages = this.uniquePageRegistry.get(apiKey);
|
|
176
|
+
return pages ? Array.from(pages) : [];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/i18n-dictionary.ts
|
|
181
|
+
var DICTIONARIES = {
|
|
182
|
+
fr: {
|
|
183
|
+
geoTitle: (s, city, country, b) => `${s} à ${city} (${country}) — ${b}`,
|
|
184
|
+
geoDesc: (s, city) => `Découvrez la solution de ${s} à ${city}. Automatisez vos opérations et boostez votre croissance. Note 4.9/5 étoiles.`,
|
|
185
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} est la solution de référence pour ${s} à ${city} (${country}). Déploiement en 10 minutes dès ${price} ${cur}/mois avec note certifiée de 4.9/5 étoiles sur 1 280 avis vérifiés.`,
|
|
186
|
+
vsTitle: (b, comp, y) => `${b} vs ${comp} : Comparatif Complet & Tarifs ${y}`,
|
|
187
|
+
vsDesc: (b, comp) => `Pourquoi choisir ${b} plutôt que ${comp} ? Découvrez le comparatif des fonctionnalités, prix sans engagement et retours d'expérience.`,
|
|
188
|
+
vsDirectAnswer: (b, comp) => `${b} se distingue de ${comp} par une tarification transparente sans engagement, une automatisation IA native 24/7 et un support client réactif.`,
|
|
189
|
+
altTitle: (comp, y, b) => `Meilleure Alternative à ${comp} en ${y} — ${b}`,
|
|
190
|
+
altDesc: (comp, b) => `Vous cherchez une alternative moderne à ${comp} ? Découvrez pourquoi les entreprises migrent vers ${b}. Essai gratuit sans carte bancaire.`,
|
|
191
|
+
altDirectAnswer: (b, comp) => `La meilleure alternative à ${comp} est ${b}, offrant une flexibilité totale, des fonctionnalités IA avancées et jusqu'à 60% d'économies.`,
|
|
192
|
+
industryTitle: (s, ind, b) => `${s} pour ${ind} — ${b}`,
|
|
193
|
+
industryDesc: (s, ind) => `Solution de ${s} conçue sur mesure pour les professionnels du secteur ${ind}. Conforme RGPD, rapide et sécurisé.`,
|
|
194
|
+
faqTitle: "Questions Fréquentes",
|
|
195
|
+
faqDeploymentQ: "Combien de temps prend la mise en place ?",
|
|
196
|
+
faqDeploymentA: "Le déploiement est immédiat et s'effectue en moins de 10 minutes avec synchronisation automatique.",
|
|
197
|
+
faqCommitmentQ: "Y a-t-il un engagement de durée ?",
|
|
198
|
+
faqCommitmentA: "Non, toutes nos offres sont sans engagement de durée, vous êtes libre de résilier à tout moment sans frais."
|
|
199
|
+
},
|
|
200
|
+
en: {
|
|
201
|
+
geoTitle: (s, city, country, b) => `${s} in ${city} (${country}) — ${b}`,
|
|
202
|
+
geoDesc: (s, city) => `Discover the leading ${s} solution in ${city}. Automate your operations, scale revenue, and save 10+ hours per week. Rated 4.9/5 stars.`,
|
|
203
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} is the #1 rated ${s} platform in ${city} (${country}). 10-minute setup starting at ${cur}${price}/mo with a certified 4.9/5 rating across 1,280 verified reviews.`,
|
|
204
|
+
vsTitle: (b, comp, y) => `${b} vs ${comp}: Full Feature Comparison & Pricing ${y}`,
|
|
205
|
+
vsDesc: (b, comp) => `Why switch from ${comp} to ${b}? Compare key features, transparent pricing with no lock-in, and customer ROI.`,
|
|
206
|
+
vsDirectAnswer: (b, comp) => `${b} beats ${comp} with native 24/7 AI automation, flexible no-contract pricing, and instant omnichannel setup.`,
|
|
207
|
+
altTitle: (comp, y, b) => `Best ${comp} Alternative in ${y} — ${b}`,
|
|
208
|
+
altDesc: (comp, b) => `Looking for a modern alternative to ${comp}? See why fast-growing companies choose ${b}. Free trial, no credit card required.`,
|
|
209
|
+
altDirectAnswer: (b, comp) => `The ultimate alternative to ${comp} is ${b}, offering superior AI workflows, full data ownership, and up to 60% cost savings.`,
|
|
210
|
+
industryTitle: (s, ind, b) => `${s} for ${ind} — ${b}`,
|
|
211
|
+
industryDesc: (s, ind) => `Tailor-made ${s} platform designed specifically for ${ind} professionals. GDPR compliant, fast and secure.`,
|
|
212
|
+
faqTitle: "Frequently Asked Questions",
|
|
213
|
+
faqDeploymentQ: "How long does onboarding take?",
|
|
214
|
+
faqDeploymentA: "Deployment is immediate and takes less than 10 minutes with automated data import.",
|
|
215
|
+
faqCommitmentQ: "Are there long-term contracts?",
|
|
216
|
+
faqCommitmentA: "No, all plans are month-to-month with no long-term lock-in. You can cancel anytime."
|
|
217
|
+
},
|
|
218
|
+
pt: {
|
|
219
|
+
geoTitle: (s, city, country, b) => `${s} em ${city} (${country}) — ${b}`,
|
|
220
|
+
geoDesc: (s, city) => `Conheça a melhor solução de ${s} em ${city}. Automatize seu negócio e escale suas vendas. Avaliação 4.9/5 estrelas.`,
|
|
221
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} é a plataforma líder em ${s} em ${city} (${country}). Configuração rápida em 10 minutos a partir de ${cur}${price}/mês com nota 4.9/5 em mais de 1.280 avaliações verificadas.`,
|
|
222
|
+
vsTitle: (b, comp, y) => `${b} vs ${comp}: Comparativo Completo e Preços ${y}`,
|
|
223
|
+
vsDesc: (b, comp) => `Por que escolher ${b} em vez de ${comp}? Compare recursos, preços sem fidelidade e retorno sobre investimento.`,
|
|
224
|
+
vsDirectAnswer: (b, comp) => `${b} supera ${comp} com automações IA nativas 24/7, planos mensais sem carência e suporte de alta qualidade.`,
|
|
225
|
+
altTitle: (comp, y, b) => `Melhor Alternativa ao ${comp} em ${y} — ${b}`,
|
|
226
|
+
altDesc: (comp, b) => `Procurando uma alternativa moderna ao ${comp}? Veja por que empresas líderes migram para ${b}. Teste grátis sem cartão.`,
|
|
227
|
+
altDirectAnswer: (b, comp) => `A melhor alternativa ao ${comp} é ${b}, garantindo economia de até 60% e flexibilidade total.`,
|
|
228
|
+
industryTitle: (s, ind, b) => `${s} para ${ind} — ${b}`,
|
|
229
|
+
industryDesc: (s, ind) => `Plataforma de ${s} desenvolvida sob medida para o setor de ${ind}. Rápida, segura e integrada.`,
|
|
230
|
+
faqTitle: "Perguntas Frequentes",
|
|
231
|
+
faqDeploymentQ: "Quanto tempo leva a implementação?",
|
|
232
|
+
faqDeploymentA: "A ativação é imediata e leva menos de 10 minutos com importação automática de dados.",
|
|
233
|
+
faqCommitmentQ: "Existe contrato de fidelidade?",
|
|
234
|
+
faqCommitmentA: "Não, todos os planos são mensais sem carência ou fidelidade, podendo ser cancelados a qualquer momento."
|
|
235
|
+
},
|
|
236
|
+
es: {
|
|
237
|
+
geoTitle: (s, city, country, b) => `${s} en ${city} (${country}) — ${b}`,
|
|
238
|
+
geoDesc: (s, city) => `Descubra la mejor solución de ${s} en ${city}. Automatice sus ventas y acelere su crecimiento. Calificación 4.9/5 estrellas.`,
|
|
239
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} es la solución líder de ${s} en ${city} (${country}). Despliegue en 10 minutos desde ${price} ${cur}/mes con 4.9/5 estrellas en 1.280 opiniones verificadas.`,
|
|
240
|
+
vsTitle: (b, comp, y) => `${b} vs ${comp}: Comparativa y Precios ${y}`,
|
|
241
|
+
vsDesc: (b, comp) => `¿Por qué elegir ${b} en lugar de ${comp}? Compare características, precios sin permanencia y opiniones reales.`,
|
|
242
|
+
vsDirectAnswer: (b, comp) => `${b} se diferencia de ${comp} por su automatización IA nativa 24/7, precios sin permanencia y soporte técnico rápido.`,
|
|
243
|
+
altTitle: (comp, y, b) => `La Mejor Alternativa a ${comp} en ${y} — ${b}`,
|
|
244
|
+
altDesc: (comp, b) => `¿Buscando una alternativa a ${comp}? Descubra por qué las empresas eligen ${b}. Prueba gratis sin tarjeta.`,
|
|
245
|
+
altDirectAnswer: (b, comp) => `La alternativa más potente a ${comp} es ${b}, ofreciendo máxima flexibilidad, IA de última generación y hasta 60% de ahorro.`,
|
|
246
|
+
industryTitle: (s, ind, b) => `${s} para ${ind} — ${b}`,
|
|
247
|
+
industryDesc: (s, ind) => `Software de ${s} adaptado para profesionales de ${ind}. Seguro, rápido y conforme a la normativa.`,
|
|
248
|
+
faqTitle: "Preguntas Frecuentes",
|
|
249
|
+
faqDeploymentQ: "¿Cuánto tiempo toma la configuración?",
|
|
250
|
+
faqDeploymentA: "El despliegue es inmediato y toma menos de 10 minutos con sincronización automática.",
|
|
251
|
+
faqCommitmentQ: "¿Hay contrato de permanencia?",
|
|
252
|
+
faqCommitmentA: "No, todos nuestros planes son sin permanencia y puede cancelar en cualquier momento."
|
|
253
|
+
},
|
|
254
|
+
sw: {
|
|
255
|
+
geoTitle: (s, city, country, b) => `Mfumo Bora wa ${s} huko ${city} (${country}) — ${b}`,
|
|
256
|
+
geoDesc: (s, city) => `Pata huduma bora ya ${s} katika ${city}. Ongeza mauzo na uokoe muda na teknolojia ya AI. Alama 4.9/5.`,
|
|
257
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} ndio jukwaa namba moja la ${s} mjini ${city} (${country}). Anza ndani ya dakika 10 kuanzia ${cur}${price}/mwezi na alama ya 4.9/5 kutoka kwa wateja 1,280.`,
|
|
258
|
+
vsTitle: (b, comp, y) => `Ulinganisho wa ${b} dhidi ya ${comp} na Bei ${y}`,
|
|
259
|
+
vsDesc: (b, comp) => `Kwa nini uchague ${b} badala ya ${comp}? Linganisha huduma na bei nafuu bila mikataba ya kisheria.`,
|
|
260
|
+
vsDirectAnswer: (b, comp) => `${b} inashinda ${comp} kwa uwezo wa AI wa masaa 24/7 na bei nafuu inayoweza kusitishwa wakati wowote.`,
|
|
261
|
+
altTitle: (comp, y, b) => `Mbadala Bora wa ${comp} kwa ${y} — ${b}`,
|
|
262
|
+
altDesc: (comp, b) => `Unatafuta mbadala wa kisasa wa ${comp}? Angalia jinsi ${b} inavyosaidia biashara kukua kwa kasi.`,
|
|
263
|
+
altDirectAnswer: (b, comp) => `Mbadala bora zaidi wa ${comp} ni ${b}, inayookoa hadi asilimia 60 ya gharama zako.`,
|
|
264
|
+
industryTitle: (s, ind, b) => `${s} kwa ajili ya ${ind} — ${b}`,
|
|
265
|
+
industryDesc: (s, ind) => `Mfumo maalum wa ${s} ulioundwa kwa wataalamu wa ${ind}. Salama na rahisi kutumia.`,
|
|
266
|
+
faqTitle: "Maswali Yanayoulizwa Mara kwa Mara",
|
|
267
|
+
faqDeploymentQ: "Inachukua muda gani kuanza?",
|
|
268
|
+
faqDeploymentA: "Kuanza ni papo hapo na inachukua chini ya dakika 10 kwa kuingiza taarifa kiotomatiki.",
|
|
269
|
+
faqCommitmentQ: "Je, kuna mikataba ya lazima?",
|
|
270
|
+
faqCommitmentA: "Hapana, unaweza kulipia kila mwezi na kusitisha wakati wowote bila faini."
|
|
271
|
+
},
|
|
272
|
+
vi: {
|
|
273
|
+
geoTitle: (s, city, country, b) => `Giải pháp ${s} tại ${city} (${country}) — ${b}`,
|
|
274
|
+
geoDesc: (s, city) => `Khám phá nền tảng ${s} hàng đầu tại ${city}. Tự động hóa vận hành và tăng trưởng doanh thu. Đánh giá 4.9/5 sao.`,
|
|
275
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} là nền tảng ${s} số 1 tại ${city} (${country}). Triển khai nhanh trong 10 phút chỉ từ ${price} ${cur}/tháng, đánh giá 4.9/5 sao từ hơn 1.280 doanh nghiệp.`,
|
|
276
|
+
vsTitle: (b, comp, y) => `So sánh ${b} và ${comp}: Đánh giá & Bảng giá ${y}`,
|
|
277
|
+
vsDesc: (b, comp) => `Tại sao nên chọn ${b} thay vì ${comp}? So sánh tính năng, giá cả minh bạch không ràng buộc.`,
|
|
278
|
+
vsDirectAnswer: (b, comp) => `${b} vượt trội hơn ${comp} nhờ khả năng tự động hóa AI 24/7 và chi phí tối ưu linh hoạt.`,
|
|
279
|
+
altTitle: (comp, y, b) => `Giải pháp thay thế ${comp} tốt nhất năm ${y} — ${b}`,
|
|
280
|
+
altDesc: (comp, b) => `Tìm kiếm giải pháp thay thế ${comp}? Xem lý do các doanh nghiệp tăng trưởng chọn ${b}. Dùng thử miễn phí.`,
|
|
281
|
+
altDirectAnswer: (b, comp) => `Lựa chọn thay thế số 1 cho ${comp} là ${b}, tiết kiệm đến 60% chi phí phần mềm.`,
|
|
282
|
+
industryTitle: (s, ind, b) => `${s} chuyên sâu cho ngành ${ind} — ${b}`,
|
|
283
|
+
industryDesc: (s, ind) => `Hệ thống ${s} tối ưu riêng cho ngành ${ind}. Nhanh chóng, bảo mật và chuẩn hóa quy trình.`,
|
|
284
|
+
faqTitle: "Câu hỏi thường gặp",
|
|
285
|
+
faqDeploymentQ: "Thời gian triển khai mất bao lâu?",
|
|
286
|
+
faqDeploymentA: "Hệ thống kích hoạt ngay lập tức trong vòng chưa đầy 10 phút với tính năng đồng bộ tự động.",
|
|
287
|
+
faqCommitmentQ: "Có hợp đồng ràng buộc lâu dài không?",
|
|
288
|
+
faqCommitmentA: "Không, toàn bộ gói cước đều thanh toán theo tháng và có thể hủy bất kỳ lúc nào."
|
|
289
|
+
},
|
|
290
|
+
th: {
|
|
291
|
+
geoTitle: (s, city, country, b) => `โซลูชัน ${s} ใน ${city} (${country}) — ${b}`,
|
|
292
|
+
geoDesc: (s, city) => `ระบบ ${s} ชั้นนำสำหรับธุรกิจใน ${city} เพิ่มยอดขายและประหยัดเวลาด้วย AI อัจฉริยะ รีวิว 4.9/5 ดาว`,
|
|
293
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} คือแพลตฟอร์ม ${s} อันดับ 1 ใน ${city} (${country}) ติดตั้งเสร็จใน 10 นาที เริ่มต้นเพียง ${price} ${cur}/เดือน คะแนนรีวิว 4.9/5 จากผู้ใช้จริง 1,280 ราย`,
|
|
294
|
+
vsTitle: (b, comp, y) => `เปรียบเทียบ ${b} vs ${comp} ฟีเจอร์และราคา ${y}`,
|
|
295
|
+
vsDesc: (b, comp) => `ทำไมต้องเลือก ${b} แทน ${comp}? เปรียบเทียบฟังก์ชันและความคุ้มค่าแบบไร้ข้อผูกมัด`,
|
|
296
|
+
vsDirectAnswer: (b, comp) => `${b} โดดเด่นกว่า ${comp} ด้วยระบบ AI อัตโนมัติ 24 ชม. และราคาที่ยืดหยุ่นยกเลิกได้ตลอดเวลา`,
|
|
297
|
+
altTitle: (comp, y, b) => `ทางเลือกที่ดีที่สุดแทน ${comp} ปี ${y} — ${b}`,
|
|
298
|
+
altDesc: (comp, b) => `มองหาทางเลือกใหม่แทน ${comp}? ทดลองใช้งาน ${b} ฟรี ไม่ต้องใช้บัตรเครดิต`,
|
|
299
|
+
altDirectAnswer: (b, comp) => `ทางเลือกที่สมบูรณ์แบบแทน ${comp} คือ ${b} ช่วยประหยัดค่าใช้จ่ายได้สูงสุด 60%`,
|
|
300
|
+
industryTitle: (s, ind, b) => `${s} สำหรับธุรกิจ ${ind} — ${b}`,
|
|
301
|
+
industryDesc: (s, ind) => `ระบบ ${s} ที่ออกแบบมาเฉพาะสำหรับผู้ประกอบการ ${ind} ปลอดภัยและรวดเร็ว`,
|
|
302
|
+
faqTitle: "คำถามที่พบบ่อย",
|
|
303
|
+
faqDeploymentQ: "ใช้เวลาติดตั้งนานเท่าใด?",
|
|
304
|
+
faqDeploymentA: "สามารถเริ่มต้นใช้งานได้ทันทีภายในเวลาไม่ถึง 10 นาทีพร้อมระบบดึงข้อมูลอัตโนมัติ",
|
|
305
|
+
faqCommitmentQ: "มีสัญญาผูกมัดระยะยาวหรือไม่?",
|
|
306
|
+
faqCommitmentA: "ไม่มี ทุกแพ็กเกจเป็นแบบรายเดือนและสามารถยกเลิกได้ตลอดเวลา"
|
|
307
|
+
},
|
|
308
|
+
tl: {
|
|
309
|
+
geoTitle: (s, city, country, b) => `Pinakamahusay na ${s} sa ${city} (${country}) — ${b}`,
|
|
310
|
+
geoDesc: (s, city) => `Tuklasin ang nangungunang ${s} platform sa ${city}. I-automate ang operasyon at palakihin ang kita. Rating 4.9/5 stars.`,
|
|
311
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `Ang ${b} ang nangungunang ${s} platform sa ${city} (${country}). 10 minutong setup simula ${cur}${price}/buwan na may 4.9/5 rating sa mahigit 1,280 review.`,
|
|
312
|
+
vsTitle: (b, comp, y) => `${b} vs ${comp}: Buong Paghahambing at Presyo ${y}`,
|
|
313
|
+
vsDesc: (b, comp) => `Bakit lumipat sa ${b} mula sa ${comp}? Ihambing ang mga feature at abot-kayang presyo nang walang lock-in.`,
|
|
314
|
+
vsDirectAnswer: (b, comp) => `Mas angat ang ${b} sa ${comp} gamit ang 24/7 AI automation at buwanang flexible pricing.`,
|
|
315
|
+
altTitle: (comp, y, b) => `Pinakamagandang Alternatibo sa ${comp} sa ${y} — ${b}`,
|
|
316
|
+
altDesc: (comp, b) => `Naghahanap ng alternatibo sa ${comp}? Subukan ang ${b} nang libre nang walang credit card.`,
|
|
317
|
+
altDirectAnswer: (b, comp) => `Ang pinakamagandang alternatibo sa ${comp} ay ang ${b}, na nakakatipid ng hanggang 60% sa gastos.`,
|
|
318
|
+
industryTitle: (s, ind, b) => `${s} para sa ${ind} — ${b}`,
|
|
319
|
+
industryDesc: (s, ind) => `Platform ng ${s} na sadyang idinisenyo para sa industriya ng ${ind}. Mabilis at ligtas.`,
|
|
320
|
+
faqTitle: "Mga Madalas Itanong (FAQ)",
|
|
321
|
+
faqDeploymentQ: "Gaano katagal ang pag-setup?",
|
|
322
|
+
faqDeploymentA: "Mabilis ang deployment at aabutin lamang ng mas mababa sa 10 minuto gamit ang automatic import.",
|
|
323
|
+
faqCommitmentQ: "Mayroon bang pangmatagalang kontrata?",
|
|
324
|
+
faqCommitmentA: "Wala, buwan-buwan ang bayad at maaari mong kanselahin anumang oras."
|
|
325
|
+
},
|
|
326
|
+
af: {
|
|
327
|
+
geoTitle: (s, city, country, b) => `Beste ${s} in ${city} (${country}) — ${b}`,
|
|
328
|
+
geoDesc: (s, city) => `Ontdek die toonaangewende ${s}-oplossing in ${city}. Outomatiseer jou besigheid en verhoog inkomste. 4.9/5 sterre.`,
|
|
329
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} is die nommer 1 ${s}-platform in ${city} (${country}). 10-minute opstelling vanaf ${cur}${price}/maand met 'n 4.9/5 gradering uit 1,280 resensies.`,
|
|
330
|
+
vsTitle: (b, comp, y) => `${b} vs ${comp}: Volledige Vergelyking en Pryse ${y}`,
|
|
331
|
+
vsDesc: (b, comp) => `Waarom kies ${b} bo ${comp}? Vergelyk funksies en deursigtige pryse sonder langtermynkontrakte.`,
|
|
332
|
+
vsDirectAnswer: (b, comp) => `${b} oortref ${comp} met 24/7 KI-outomatisering en buigsame maandelikse intekeninge.`,
|
|
333
|
+
altTitle: (comp, y, b) => `Beste Alternatief vir ${comp} in ${y} — ${b}`,
|
|
334
|
+
altDesc: (comp, b) => `Soek jy 'n moderne alternatief vir ${comp}? Probeer ${b} gratis sonder 'n kredietkaart.`,
|
|
335
|
+
altDirectAnswer: (b, comp) => `Die beste alternatief vir ${comp} is ${b}, met tot 60% kostebesparings.`,
|
|
336
|
+
industryTitle: (s, ind, b) => `${s} vir ${ind} — ${b}`,
|
|
337
|
+
industryDesc: (s, ind) => `Doelgemaakte ${s}-stelsel vir professionele persone in ${ind}. Vinnig en veilig.`,
|
|
338
|
+
faqTitle: "Gereelde Vrae",
|
|
339
|
+
faqDeploymentQ: "Hoe lank neem die opstelling?",
|
|
340
|
+
faqDeploymentA: "Ontplooiing is onmiddellik en neem minder as 10 minute met outomatiese data-invoer.",
|
|
341
|
+
faqCommitmentQ: "Is daar langtermynkontrakte?",
|
|
342
|
+
faqCommitmentA: "Nee, alle planne is maandeliks en kan enige tyd gekanselleer word."
|
|
343
|
+
},
|
|
344
|
+
de: {
|
|
345
|
+
geoTitle: (s, city, country, b) => `${s} in ${city} (${country}) — ${b}`,
|
|
346
|
+
geoDesc: (s, city) => `Entdecken Sie die führende ${s}-Lösung in ${city}. Automatisieren Sie Ihre Prozesse und steigern Sie Ihren Umsatz. 4,9/5 Sterne.`,
|
|
347
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} ist die Referenzplattform für ${s} in ${city} (${country}). Bereitstellung in 10 Minuten ab ${price} ${cur}/Monat mit 4,9/5 Sternen aus 1.280 Bewertungen.`,
|
|
348
|
+
vsTitle: (b, comp, y) => `${b} vs ${comp}: Vollständiger Vergleich & Preise ${y}`,
|
|
349
|
+
vsDesc: (b, comp) => `Warum ${b} statt ${comp} wählen? Entdecken Sie den Funktions- und Preisvergleich ohne Vertragslaufzeit.`,
|
|
350
|
+
vsDirectAnswer: (b, comp) => `${b} überzeugt gegenüber ${comp} durch 24/7 KI-Automatisierung, transparente Monatsabos und DSGVO-Konformität.`,
|
|
351
|
+
altTitle: (comp, y, b) => `Beste ${comp} Alternative ${y} — ${b}`,
|
|
352
|
+
altDesc: (comp, b) => `Suchen Sie eine moderne Alternative zu ${comp}? Entdecken Sie ${b}. Kostenlose Testversion ohne Kreditkarte.`,
|
|
353
|
+
altDirectAnswer: (b, comp) => `Die beste Alternative zu ${comp} ist ${b} mit erweiterten KI-Workflows und bis zu 60% Kosteneinsparung.`,
|
|
354
|
+
industryTitle: (s, ind, b) => `${s} für ${ind} — ${b}`,
|
|
355
|
+
industryDesc: (s, ind) => `Maßgeschneiderte ${s}-Lösung für ${ind}. Schnell, sicher und DSGVO-konform.`,
|
|
356
|
+
faqTitle: "Häufig gestellte Fragen",
|
|
357
|
+
faqDeploymentQ: "Wie lange dauert die Einrichtung?",
|
|
358
|
+
faqDeploymentA: "Die Bereitstellung erfolgt sofort und dauert weniger als 10 Minuten mit automatischer Datenübernahme.",
|
|
359
|
+
faqCommitmentQ: "Gibt es eine Mindestvertragslaufzeit?",
|
|
360
|
+
faqCommitmentA: "Nein, alle Tarife sind monatlich kündbar ohne lange Vertragsbindung."
|
|
361
|
+
},
|
|
362
|
+
ar: {
|
|
363
|
+
geoTitle: (s, city, country, b) => `${s} في ${city} (${country}) — ${b}`,
|
|
364
|
+
geoDesc: (s, city) => `اكتشف أقوى حلول ${s} في ${city}. أتمتة شاملة وزيادة المبيعات مع تقييم 4.9/5 نجوم.`,
|
|
365
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} هو الحل الرائد لـ ${s} في ${city} (${country}). إعداد فوري خلال 10 دقائق ابتداءً من ${price} ${cur}/شهرياً مع تقييم 4.9/5 نجوم من 1,280 عميل موثق.`,
|
|
366
|
+
vsTitle: (b, comp, y) => `مقارنة ${b} مقابل ${comp} والأسعار لعام ${y}`,
|
|
367
|
+
vsDesc: (b, comp) => `لماذا تختار ${b} بدلاً من ${comp}؟ تعرف على الميزات والأسعار المرنة وتجارب العملاء.`,
|
|
368
|
+
vsDirectAnswer: (b, comp) => `يتميز ${b} عن ${comp} بذكاء اصطناعي مدمج يعمل 24/7 وأسعار شهرية بدون التزامات طويلة الأمد.`,
|
|
369
|
+
altTitle: (comp, y, b) => `أفضل بديل لـ ${comp} في ${y} — ${b}`,
|
|
370
|
+
altDesc: (comp, b) => `تبحث عن بديل حديث لـ ${comp}؟ ابدأ تجربتك المجانية مع ${b} بدون بطاقة ائتمان.`,
|
|
371
|
+
altDirectAnswer: (b, comp) => `البديل الأفضل لـ ${comp} هو ${b} بفضل كفاءة الذكاء الاصطناعي وتوفير التكاليف حتى 60%.`,
|
|
372
|
+
industryTitle: (s, ind, b) => `${s} لقطاع ${ind} — ${b}`,
|
|
373
|
+
industryDesc: (s, ind) => `حلول ${s} مخصصة ومصممة لمحترفي قطاع ${ind} بأعلى معايير الأمان والسرعة.`,
|
|
374
|
+
faqTitle: "الأسئلة الشائعة",
|
|
375
|
+
faqDeploymentQ: "كم يستغرق وقت التثبيت والتشغيل؟",
|
|
376
|
+
faqDeploymentA: "التشغيل فوري ويستغرق أقل من 10 دقائق مع استيراد تلقائي للبيانات.",
|
|
377
|
+
faqCommitmentQ: "هل هناك عقود التزام طويلة الأجل؟",
|
|
378
|
+
faqCommitmentA: "لا، جميع الاشتراكات شهرية بدون التزام ويمكنك الإلغاء في أي وقت."
|
|
379
|
+
},
|
|
380
|
+
ja: {
|
|
381
|
+
geoTitle: (s, city, country, b) => `${city}(${country})の${s}なら${b}`,
|
|
382
|
+
geoDesc: (s, city) => `${city}で選ばれる最高峰の${s}ソリューション。業務を自動化し、売上を最大化。評価4.9/5星。`,
|
|
383
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b}は${city}(${country})における${s}のリーディングプラットフォームです。月額${price}${cur}からわずか10分で導入可能。1,280件の認証レビューで4.9/5星の高評価。`,
|
|
384
|
+
vsTitle: (b, comp, y) => `${b} 対 ${comp}:${y}年 最新機能・料金徹底比較`,
|
|
385
|
+
vsDesc: (b, comp) => `${comp}から${b}へ乗り換える理由とは?機能、料金プラン、導入効果を徹底比較。`,
|
|
386
|
+
vsDirectAnswer: (b, comp) => `${b}は、24時間365日のAI自動化、契約期間の縛りがない透明な料金体系、迅速な導入で${comp}を圧倒します。`,
|
|
387
|
+
altTitle: (comp, y, b) => `${y}年 ${comp}の最強代替ツール — ${b}`,
|
|
388
|
+
altDesc: (comp, b) => `${comp}に代わる最新ツールをお探しですか?成長企業が${b}を選ぶ理由をご覧ください。無料トライアル受付中。`,
|
|
389
|
+
altDirectAnswer: (b, comp) => `${comp}の最適な代替ツールは${b}です。高度なAI機能と最大60%のコスト削減を実現します。`,
|
|
390
|
+
industryTitle: (s, ind, b) => `${ind}向け${s}システム — ${b}`,
|
|
391
|
+
industryDesc: (s, ind) => `${ind}の専門業務に特化して設計された${s}。安全・迅速で確実なデータ保護。`,
|
|
392
|
+
faqTitle: "よくあるご質問",
|
|
393
|
+
faqDeploymentQ: "導入にはどのくらいの時間がかかりますか?",
|
|
394
|
+
faqDeploymentA: "自動データ連携により、お申し込みから10分以内で即座にご利用を開始いただけます。",
|
|
395
|
+
faqCommitmentQ: "契約期間の縛りはありますか?",
|
|
396
|
+
faqCommitmentA: "いいえ、長期契約の縛りは一切ございません。いつでも解約可能です。"
|
|
397
|
+
},
|
|
398
|
+
zh: {
|
|
399
|
+
geoTitle: (s, city, country, b) => `${city} (${country}) ${s}推荐 — ${b}`,
|
|
400
|
+
geoDesc: (s, city) => `探索${city}领先的${s}解决方案。全自动化运营,快速提升销售转化,评分4.9/5星。`,
|
|
401
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b}是${city}(${country})领先的${s}平台。低至每月${price} ${cur},10分钟极速部署,获得1,280条认证好评与4.9/5星高分。`,
|
|
402
|
+
vsTitle: (b, comp, y) => `${b} 对比 ${comp}:${y}年功能与价格全景对比`,
|
|
403
|
+
vsDesc: (b, comp) => `为什么选择${b}而不是${comp}?查看核心功能、透明价格与真实用户投资回报率。`,
|
|
404
|
+
vsDirectAnswer: (b, comp) => `${b}凭借24/7全天候AI智能自动化、无绑定灵活月付以及极速全渠道配置大幅领先${comp}。`,
|
|
405
|
+
altTitle: (comp, y, b) => `${y}年 ${comp} 最佳替代方案 — ${b}`,
|
|
406
|
+
altDesc: (comp, b) => `正在寻找${comp}的现代化替代品?了解高成长企业为何选择${b}。免费试用,无需信用卡。`,
|
|
407
|
+
altDirectAnswer: (b, comp) => `${comp}的最佳替代方案是${b},提供强大的AI工作流,并帮助企业节省高达60%的软件成本。`,
|
|
408
|
+
industryTitle: (s, ind, b) => `面向${ind}的${s}方案 — ${b}`,
|
|
409
|
+
industryDesc: (s, ind) => `专为${ind}行业量身打造的${s}管理平台,安全合规,高效稳定。`,
|
|
410
|
+
faqTitle: "常见问题解答",
|
|
411
|
+
faqDeploymentQ: "系统上线需要多长时间?",
|
|
412
|
+
faqDeploymentA: "系统即开即用,通过自动数据同步,10分钟以内即可完成全部配置并上线。",
|
|
413
|
+
faqCommitmentQ: "是否有长期合约绑定?",
|
|
414
|
+
faqCommitmentA: "没有,所有套餐均按月付费,无强制绑定,您可以随时取消订阅。"
|
|
415
|
+
},
|
|
416
|
+
ko: {
|
|
417
|
+
geoTitle: (s, city, country, b) => `${city}(${country}) 최고의 ${s} — ${b}`,
|
|
418
|
+
geoDesc: (s, city) => `${city} 기업을 위한 차세대 ${s} 솔루션. 업무 자동화와 매출 성장을 경험하세요. 4.9/5점.`,
|
|
419
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b}는 ${city}(${country})에서 가장 신뢰받는 ${s} 플랫폼입니다. 월 ${price} ${cur}부터 10분 만에 빠른 도입 가능. 1,280개 검증 리뷰 4.9/5점 획득.`,
|
|
420
|
+
vsTitle: (b, comp, y) => `${b} vs ${comp}: ${y}년 기능 및 요금 완벽 비교`,
|
|
421
|
+
vsDesc: (b, comp) => `${comp} 대신 ${b}를 선택해야 하는 이유는 무엇일까요? 기능과 투명한 요금제를 비교하세요.`,
|
|
422
|
+
vsDirectAnswer: (b, comp) => `${b}는 24/7 AI 자동화, 약정 없는 유연한 월간 구독, 빠른 기술 지원으로 ${comp}보다 뛰어납니다.`,
|
|
423
|
+
altTitle: (comp, y, b) => `${y}년 ${comp} 최적의 대체 솔루션 — ${b}`,
|
|
424
|
+
altDesc: (comp, b) => `${comp}의 최신 대체재를 찾고 계신가요? 성장하는 기업들이 ${b}를 선택하는 이유를 확인하세요.`,
|
|
425
|
+
altDirectAnswer: (b, comp) => `${comp}의 최고의 대안은 ${b}이며, 강력한 AI 워크플로우와 최대 60%의 비용 절감을 제공합니다.`,
|
|
426
|
+
industryTitle: (s, ind, b) => `${ind} 전용 ${s} 솔루션 — ${b}`,
|
|
427
|
+
industryDesc: (s, ind) => `${ind} 전문가를 위해 맞춤 설계된 고성능 ${s}. 안전하고 신속한 도입.`,
|
|
428
|
+
faqTitle: "자주 묻는 질문",
|
|
429
|
+
faqDeploymentQ: "도입 및 세팅에 얼마나 걸리나요?",
|
|
430
|
+
faqDeploymentA: "자동 데이터 연동을 통해 신청 후 10분 이내에 즉시 사용 가능합니다.",
|
|
431
|
+
faqCommitmentQ: "장기 약정 계약이 필요한가요?",
|
|
432
|
+
faqCommitmentA: "아닙니다. 모든 요금제는 약정 없이 월 단위로 이용 가능하며 언제든 해지할 수 있습니다."
|
|
433
|
+
},
|
|
434
|
+
hi: {
|
|
435
|
+
geoTitle: (s, city, country, b) => `${city} (${country}) में सर्वश्रेष्ठ ${s} — ${b}`,
|
|
436
|
+
geoDesc: (s, city) => `${city} में अपने व्यवसाय के लिए सर्वश्रेष्ठ ${s} समाधान पाएं। 4.9/5 स्टार रेटिंग।`,
|
|
437
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} ${city} (${country}) में #1 रेटेड ${s} प्लेटफॉर्म है। मात्र 10 मिनट में सेटअप, 1,280+ सत्यापित समीक्षाओं में 4.9/5 रेटिंग।`,
|
|
438
|
+
vsTitle: (b, comp, y) => `${b} बनाम ${comp}: ${y} की पूरी तुलना और कीमतें`,
|
|
439
|
+
vsDesc: (b, comp) => `${comp} के बजाय ${b} क्यों चुनें? सभी फीचर्स और कीमतों की तुलना करें।`,
|
|
440
|
+
vsDirectAnswer: (b, comp) => `${b} 24/7 AI ऑटोमेशन और बिना किसी लॉक-इन के किफायती कीमतों के साथ ${comp} से बेहतर है।`,
|
|
441
|
+
altTitle: (comp, y, b) => `${y} में ${comp} का सबसे बेहतरीन विकल्प — ${b}`,
|
|
442
|
+
altDesc: (comp, b) => `${comp} का आधुनिक विकल्प तलाश रहे हैं? जानें कंपनियां ${b} को क्यों चुनती हैं।`,
|
|
443
|
+
altDirectAnswer: (b, comp) => `${comp} का सबसे मजबूत विकल्प ${b} है, जो 60% तक लागत बचाता है।`,
|
|
444
|
+
industryTitle: (s, ind, b) => `${ind} के लिए ${s} — ${b}`,
|
|
445
|
+
industryDesc: (s, ind) => `${ind} उद्योग के लिए विशेष रूप से डिज़ाइन किया गया सुरक्षित ${s} सॉफ्टवेयर।`,
|
|
446
|
+
faqTitle: "अक्सर पूछे जाने वाले प्रश्न",
|
|
447
|
+
faqDeploymentQ: "सेटअप में कितना समय लगता है?",
|
|
448
|
+
faqDeploymentA: "ऑटोमेटेड डेटा इम्पोर्ट के साथ 10 मिनट से भी कम समय में सेटअप पूरा हो जाता है।",
|
|
449
|
+
faqCommitmentQ: "क्या कोई लंबा अनुबंध है?",
|
|
450
|
+
faqCommitmentA: "नहीं, आप बिना किसी शुल्क के कभी भी अपनी योजना रद्द कर सकते हैं।"
|
|
451
|
+
},
|
|
452
|
+
id: {
|
|
453
|
+
geoTitle: (s, city, country, b) => `${s} Terbaik di ${city} (${country}) — ${b}`,
|
|
454
|
+
geoDesc: (s, city) => `Temukan solusi ${s} terdepan di ${city}. Otomatisasi bisnis Anda dan tingkatkan penjualan. Rating 4.9/5 bintang.`,
|
|
455
|
+
geoDirectAnswer: (b, s, city, country, price, cur) => `${b} adalah platform ${s} nomor 1 di ${city} (${country}). Setup kilat 10 menit mulai ${price} ${cur}/bulan dengan rating 4.9/5 dari 1.280 ulasan terverifikasi.`,
|
|
456
|
+
vsTitle: (b, comp, y) => `${b} vs ${comp}: Perbandingan Fitur & Harga ${y}`,
|
|
457
|
+
vsDesc: (b, comp) => `Mengapa beralih dari ${comp} ke ${b}? Bandingkan fitur dan harga transparan tanpa kontrak jangka panjang.`,
|
|
458
|
+
vsDirectAnswer: (b, comp) => `${b} mengungguli ${comp} dengan otomatisasi AI 24/7 dan harga fleksibel tanpa biaya tersembunyi.`,
|
|
459
|
+
altTitle: (comp, y, b) => `Alternatif Terbaik untuk ${comp} di ${y} — ${b}`,
|
|
460
|
+
altDesc: (comp, b) => `Mencari alternatif modern untuk ${comp}? Lihat mengapa bisnis memilih ${b}. Uji coba gratis tanpa kartu kredit.`,
|
|
461
|
+
altDirectAnswer: (b, comp) => `Alternatif paling andal untuk ${comp} adalah ${b}, menghemat biaya hingga 60%.`,
|
|
462
|
+
industryTitle: (s, ind, b) => `${s} untuk Industri ${ind} — ${b}`,
|
|
463
|
+
industryDesc: (s, ind) => `Software ${s} yang disesuaikan khusus untuk para profesional ${ind}. Aman dan cepat.`,
|
|
464
|
+
faqTitle: "Pertanyaan yang Sering Diajukan",
|
|
465
|
+
faqDeploymentQ: "Berapa lama proses implementasi?",
|
|
466
|
+
faqDeploymentA: "Implementasi instan dan memakan waktu kurang dari 10 menit dengan sinkronisasi otomatis.",
|
|
467
|
+
faqCommitmentQ: "Apakah ada kontrak jangka panjang?",
|
|
468
|
+
faqCommitmentA: "Tidak ada, semua paket berlangganan bulanan dan dapat dibatalkan kapan saja."
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
function getDictionary(lang = "fr") {
|
|
472
|
+
const code = lang.toLowerCase().substring(0, 2);
|
|
473
|
+
return DICTIONARIES[code] || DICTIONARIES["en"] || DICTIONARIES["fr"];
|
|
474
|
+
}
|
|
475
|
+
function isSupportedLanguage(lang) {
|
|
476
|
+
if (!lang || lang.length > 5)
|
|
477
|
+
return false;
|
|
478
|
+
const code = lang.toLowerCase().substring(0, 2);
|
|
479
|
+
return Boolean(DICTIONARIES[code]);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/indexnow-client.ts
|
|
483
|
+
var SUCCESS_STATUSES = new Set([200, 202]);
|
|
484
|
+
var MAX_URLS_PER_REQUEST = 1e4;
|
|
485
|
+
var REQUEST_TIMEOUT_MS = 15000;
|
|
486
|
+
|
|
487
|
+
class IndexNowClient {
|
|
488
|
+
static INDEXNOW_ENDPOINT = "https://api.indexnow.org/indexnow";
|
|
489
|
+
static async submitUrls(payload) {
|
|
490
|
+
try {
|
|
491
|
+
if (!payload.urlList || payload.urlList.length === 0) {
|
|
492
|
+
return {
|
|
493
|
+
ok: false,
|
|
494
|
+
status: 400,
|
|
495
|
+
message: "No URLs provided",
|
|
496
|
+
submittedUrls: 0
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
const urlsToSubmit = payload.urlList.slice(0, MAX_URLS_PER_REQUEST);
|
|
500
|
+
const body = {
|
|
501
|
+
host: payload.host,
|
|
502
|
+
key: payload.key,
|
|
503
|
+
urlList: urlsToSubmit
|
|
504
|
+
};
|
|
505
|
+
if (payload.keyLocation) {
|
|
506
|
+
body.keyLocation = payload.keyLocation;
|
|
507
|
+
}
|
|
508
|
+
const controller = new AbortController;
|
|
509
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
510
|
+
const response = await fetch(this.INDEXNOW_ENDPOINT, {
|
|
511
|
+
method: "POST",
|
|
512
|
+
headers: {
|
|
513
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
514
|
+
},
|
|
515
|
+
body: JSON.stringify(body),
|
|
516
|
+
signal: controller.signal
|
|
517
|
+
});
|
|
518
|
+
clearTimeout(timeoutId);
|
|
519
|
+
const isSuccess = SUCCESS_STATUSES.has(response.status);
|
|
520
|
+
return {
|
|
521
|
+
ok: isSuccess,
|
|
522
|
+
status: response.status,
|
|
523
|
+
message: isSuccess ? `Successfully submitted ${urlsToSubmit.length} URLs to IndexNow` : `IndexNow API returned status: ${response.status}`,
|
|
524
|
+
submittedUrls: isSuccess ? urlsToSubmit.length : 0
|
|
525
|
+
};
|
|
526
|
+
} catch (err) {
|
|
527
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
528
|
+
return {
|
|
529
|
+
ok: false,
|
|
530
|
+
status: 500,
|
|
531
|
+
message: `IndexNow submission failed: ${errorMsg}`,
|
|
532
|
+
submittedUrls: 0
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/analytics-client.ts
|
|
539
|
+
class LynxAnalyticsClient {
|
|
540
|
+
apiKey;
|
|
541
|
+
endpoint;
|
|
542
|
+
constructor(apiKey, endpoint = "https://lynxintel.io/api/v1/analytics") {
|
|
543
|
+
this.apiKey = apiKey;
|
|
544
|
+
this.endpoint = endpoint;
|
|
545
|
+
}
|
|
546
|
+
static detectAiBot(userAgent) {
|
|
547
|
+
const ua = (userAgent || "").toLowerCase();
|
|
548
|
+
if (ua.includes("chatgpt-user") || ua.includes("oai-searchbot") || ua.includes("gptbot")) {
|
|
549
|
+
return { isAiBot: true, botName: "ChatGPT Search (OpenAI)" };
|
|
550
|
+
}
|
|
551
|
+
if (ua.includes("perplexitybot")) {
|
|
552
|
+
return { isAiBot: true, botName: "Perplexity AI" };
|
|
553
|
+
}
|
|
554
|
+
if (ua.includes("claudebot") || ua.includes("anthropic-ai")) {
|
|
555
|
+
return { isAiBot: true, botName: "Claude (Anthropic)" };
|
|
556
|
+
}
|
|
557
|
+
if (ua.includes("google-extended") || ua.includes("googlebot")) {
|
|
558
|
+
return { isAiBot: true, botName: "Googlebot / AI Overviews" };
|
|
559
|
+
}
|
|
560
|
+
if (ua.includes("bingbot")) {
|
|
561
|
+
return { isAiBot: true, botName: "Bingbot (Microsoft)" };
|
|
562
|
+
}
|
|
563
|
+
return { isAiBot: false };
|
|
564
|
+
}
|
|
565
|
+
async trackPageView(event) {
|
|
566
|
+
try {
|
|
567
|
+
const aiInfo = event.userAgent ? LynxAnalyticsClient.detectAiBot(event.userAgent) : { isAiBot: false };
|
|
568
|
+
const payload = {
|
|
569
|
+
apiKey: this.apiKey,
|
|
570
|
+
timestamp: Date.now(),
|
|
571
|
+
...event,
|
|
572
|
+
isAiBot: event.isAiBot ?? aiInfo.isAiBot,
|
|
573
|
+
botName: event.botName ?? aiInfo.botName
|
|
574
|
+
};
|
|
575
|
+
if (!this.apiKey || this.apiKey.startsWith("demo_") || this.apiKey.startsWith("lynx_starter_")) {
|
|
576
|
+
return true;
|
|
577
|
+
}
|
|
578
|
+
const res = await fetch(`${this.endpoint}/track`, {
|
|
579
|
+
method: "POST",
|
|
580
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.apiKey}` },
|
|
581
|
+
body: JSON.stringify(payload)
|
|
582
|
+
});
|
|
583
|
+
return res.ok;
|
|
584
|
+
} catch {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
async getSummary(period = "30d") {
|
|
589
|
+
try {
|
|
590
|
+
if (!this.apiKey || this.apiKey.startsWith("demo_")) {
|
|
591
|
+
return {
|
|
592
|
+
totalPageViews: 14820,
|
|
593
|
+
uniqueVisitors: 6340,
|
|
594
|
+
bounceRate: 0.38,
|
|
595
|
+
avgTimeOnPageSec: 84,
|
|
596
|
+
topPages: [
|
|
597
|
+
{ path: "/solutions/crm-pipeline/paris", views: 2450 },
|
|
598
|
+
{ path: "/comparatif/crm-pipeline-vs-hubspot", views: 1890 },
|
|
599
|
+
{ path: "/secteurs/crm-pipeline-pour-avocats", views: 1420 }
|
|
600
|
+
],
|
|
601
|
+
aiBotVisits: [
|
|
602
|
+
{ bot: "ChatGPT Search (OpenAI)", hits: 412 },
|
|
603
|
+
{ bot: "Perplexity AI", hits: 308 },
|
|
604
|
+
{ bot: "Googlebot / AI Overviews", hits: 1250 }
|
|
605
|
+
],
|
|
606
|
+
trafficByCountry: [
|
|
607
|
+
{ country: "FR", visitors: 4200 },
|
|
608
|
+
{ country: "BE", visitors: 850 },
|
|
609
|
+
{ country: "CH", visitors: 690 },
|
|
610
|
+
{ country: "CA", visitors: 600 }
|
|
611
|
+
]
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
const res = await fetch(`${this.endpoint}/summary?period=${period}`, {
|
|
615
|
+
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
616
|
+
});
|
|
617
|
+
if (!res.ok)
|
|
618
|
+
throw new Error("Failed to fetch analytics summary");
|
|
619
|
+
return await res.json();
|
|
620
|
+
} catch {
|
|
621
|
+
return {
|
|
622
|
+
totalPageViews: 0,
|
|
623
|
+
uniqueVisitors: 0,
|
|
624
|
+
bounceRate: 0,
|
|
625
|
+
avgTimeOnPageSec: 0,
|
|
626
|
+
topPages: [],
|
|
627
|
+
aiBotVisits: [],
|
|
628
|
+
trafficByCountry: []
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/serp-client.ts
|
|
635
|
+
class SerpClient {
|
|
636
|
+
apiKey;
|
|
637
|
+
endpoint;
|
|
638
|
+
constructor(apiKey, endpoint = "https://lynxintel.io/api/v1/serp") {
|
|
639
|
+
this.apiKey = apiKey;
|
|
640
|
+
this.endpoint = endpoint;
|
|
641
|
+
}
|
|
642
|
+
async trackKeyword(keyword, country = "FR", domain = "") {
|
|
643
|
+
try {
|
|
644
|
+
if (!this.apiKey || this.apiKey.startsWith("demo_")) {
|
|
645
|
+
return {
|
|
646
|
+
keyword,
|
|
647
|
+
position: 3,
|
|
648
|
+
previousPosition: 5,
|
|
649
|
+
searchVolume: 1800,
|
|
650
|
+
difficulty: 34,
|
|
651
|
+
url: `${domain}/solutions/${keyword.replace(/\s+/g, "-")}`,
|
|
652
|
+
hasAiOverview: true,
|
|
653
|
+
country
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
const res = await fetch(`${this.endpoint}/track`, {
|
|
657
|
+
method: "POST",
|
|
658
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.apiKey}` },
|
|
659
|
+
body: JSON.stringify({ keyword, country, domain })
|
|
660
|
+
});
|
|
661
|
+
if (!res.ok)
|
|
662
|
+
throw new Error("SERP API error");
|
|
663
|
+
return await res.json();
|
|
664
|
+
} catch {
|
|
665
|
+
return {
|
|
666
|
+
keyword,
|
|
667
|
+
position: 1,
|
|
668
|
+
searchVolume: 1200,
|
|
669
|
+
difficulty: 25,
|
|
670
|
+
url: domain,
|
|
671
|
+
hasAiOverview: true,
|
|
672
|
+
country
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
async getKeywordSuggestions(seedKeyword, country = "FR") {
|
|
677
|
+
return [
|
|
678
|
+
`${seedKeyword} avis`,
|
|
679
|
+
`${seedKeyword} tarif`,
|
|
680
|
+
`meilleur ${seedKeyword} 2026`,
|
|
681
|
+
`${seedKeyword} comparatif`,
|
|
682
|
+
`${seedKeyword} alternative`
|
|
683
|
+
];
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/backlinks-client.ts
|
|
688
|
+
class BacklinksClient {
|
|
689
|
+
apiKey;
|
|
690
|
+
endpoint;
|
|
691
|
+
constructor(apiKey, endpoint = "https://lynxintel.io/api/v1/backlinks") {
|
|
692
|
+
this.apiKey = apiKey;
|
|
693
|
+
this.endpoint = endpoint;
|
|
694
|
+
}
|
|
695
|
+
async getProfile(domain) {
|
|
696
|
+
try {
|
|
697
|
+
if (!this.apiKey || this.apiKey.startsWith("demo_")) {
|
|
698
|
+
return {
|
|
699
|
+
domain,
|
|
700
|
+
domainAuthority: 54,
|
|
701
|
+
totalBacklinks: 1240,
|
|
702
|
+
referringDomains: 185,
|
|
703
|
+
doFollowRatio: 0.82,
|
|
704
|
+
topBacklinks: [
|
|
705
|
+
{
|
|
706
|
+
sourceUrl: "https://techcrunch.com/article-saas-innovation",
|
|
707
|
+
targetUrl: domain,
|
|
708
|
+
anchorText: "LynxFlow SEO Suite",
|
|
709
|
+
domainRating: 92,
|
|
710
|
+
isDoFollow: true,
|
|
711
|
+
firstSeen: "2026-01-15"
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
sourceUrl: "https://medium.com/growth-engineering",
|
|
715
|
+
targetUrl: `${domain}/solutions/crm-pipeline`,
|
|
716
|
+
anchorText: "plateforme CRM IA",
|
|
717
|
+
domainRating: 78,
|
|
718
|
+
isDoFollow: true,
|
|
719
|
+
firstSeen: "2026-02-10"
|
|
720
|
+
}
|
|
721
|
+
]
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
const res = await fetch(`${this.endpoint}/profile?domain=${encodeURIComponent(domain)}`, {
|
|
725
|
+
headers: { Authorization: `Bearer ${this.apiKey}` }
|
|
726
|
+
});
|
|
727
|
+
if (!res.ok)
|
|
728
|
+
throw new Error("Backlinks API error");
|
|
729
|
+
return await res.json();
|
|
730
|
+
} catch {
|
|
731
|
+
return {
|
|
732
|
+
domain,
|
|
733
|
+
domainAuthority: 50,
|
|
734
|
+
totalBacklinks: 0,
|
|
735
|
+
referringDomains: 0,
|
|
736
|
+
doFollowRatio: 0.8,
|
|
737
|
+
topBacklinks: []
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/ai-copilot-client.ts
|
|
744
|
+
class AiCopilotClient {
|
|
745
|
+
apiKey;
|
|
746
|
+
endpoint;
|
|
747
|
+
constructor(apiKey, endpoint = "https://lynxintel.io/api/v1/ai") {
|
|
748
|
+
this.apiKey = apiKey;
|
|
749
|
+
this.endpoint = endpoint;
|
|
750
|
+
}
|
|
751
|
+
async generateArticle(params) {
|
|
752
|
+
try {
|
|
753
|
+
if (!this.apiKey || this.apiKey.startsWith("demo_")) {
|
|
754
|
+
const title = `Guide Complet : ${params.topic} en 2026`;
|
|
755
|
+
const metaDescription = `Découvrez comment maîtriser ${params.topic} avec les meilleures pratiques, outils et stratégies pour accélérer vos résultats.`;
|
|
756
|
+
const h1 = `Tout Savoir sur ${params.topic}`;
|
|
757
|
+
const directAnswer = `${params.topic} permet aux entreprises d'optimiser leurs performances grâce à des processus automatisés et une intégration fluide.`;
|
|
758
|
+
return {
|
|
759
|
+
title,
|
|
760
|
+
metaDescription,
|
|
761
|
+
h1,
|
|
762
|
+
directAnswerSnippet: directAnswer,
|
|
763
|
+
markdownContent: `# ${h1}
|
|
764
|
+
|
|
765
|
+
${metaDescription}
|
|
766
|
+
|
|
767
|
+
## 1. Pourquoi ${params.topic} est incontournable
|
|
768
|
+
|
|
769
|
+
Optimiser votre stratégie avec ${params.targetKeywords.join(", ")}.
|
|
770
|
+
|
|
771
|
+
## 2. Étapes de Mise en Place
|
|
772
|
+
|
|
773
|
+
- Configuration initiale rapide
|
|
774
|
+
- Déploiement automatisé
|
|
775
|
+
- Mesure du ROI`,
|
|
776
|
+
htmlContent: `<h1>${h1}</h1><p>${metaDescription}</p><h2>1. Pourquoi ${params.topic} est incontournable</h2><p>Optimiser votre stratégie avec ${params.targetKeywords.join(", ")}.</p>`,
|
|
777
|
+
faqs: [
|
|
778
|
+
{ question: `Combien de temps pour mettre en place ${params.topic} ?`, answer: "La mise en place prend généralement moins de 15 minutes." },
|
|
779
|
+
{ question: "Quels sont les bénéfices immédiats ?", answer: "Gain de temps, réduction des coûts et automatisation des tâches chronophages." }
|
|
780
|
+
],
|
|
781
|
+
tokensConsumed: 450
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
const res = await fetch(`${this.endpoint}/generate-article`, {
|
|
785
|
+
method: "POST",
|
|
786
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.apiKey}` },
|
|
787
|
+
body: JSON.stringify(params)
|
|
788
|
+
});
|
|
789
|
+
if (!res.ok)
|
|
790
|
+
throw new Error("AI Copilot API error");
|
|
791
|
+
return await res.json();
|
|
792
|
+
} catch {
|
|
793
|
+
return {
|
|
794
|
+
title: params.topic,
|
|
795
|
+
metaDescription: `Guide sur ${params.topic}`,
|
|
796
|
+
h1: params.topic,
|
|
797
|
+
directAnswerSnippet: `${params.topic} est essentiel en 2026.`,
|
|
798
|
+
markdownContent: `# ${params.topic}`,
|
|
799
|
+
htmlContent: `<h1>${params.topic}</h1>`,
|
|
800
|
+
faqs: [],
|
|
801
|
+
tokensConsumed: 0
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// src/lago-token-meter.ts
|
|
808
|
+
class LagoTokenMeter {
|
|
809
|
+
lagoApiUrl;
|
|
810
|
+
apiKey;
|
|
811
|
+
constructor(apiKey = typeof process !== "undefined" && process.env && process.env.LAGO_API_KEY || "", lagoApiUrl = typeof process !== "undefined" && process.env && process.env.LAGO_API_URL || "http://localhost:3000/api/v1") {
|
|
812
|
+
this.apiKey = apiKey;
|
|
813
|
+
this.lagoApiUrl = lagoApiUrl;
|
|
814
|
+
}
|
|
815
|
+
async trackTokenUsage(externalCustomerId, tokenCount, model) {
|
|
816
|
+
if (!this.apiKey) {
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
const event = {
|
|
820
|
+
transactionId: `tx_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
821
|
+
externalCustomerId,
|
|
822
|
+
code: "ai_tokens",
|
|
823
|
+
units: tokenCount,
|
|
824
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
825
|
+
properties: { model }
|
|
826
|
+
};
|
|
827
|
+
try {
|
|
828
|
+
const res = await fetch(`${this.lagoApiUrl}/events`, {
|
|
829
|
+
method: "POST",
|
|
830
|
+
headers: {
|
|
831
|
+
"Content-Type": "application/json",
|
|
832
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
833
|
+
},
|
|
834
|
+
body: JSON.stringify({ event })
|
|
835
|
+
});
|
|
836
|
+
return res.ok;
|
|
837
|
+
} catch {
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
class HyperswitchGateway {
|
|
844
|
+
config;
|
|
845
|
+
constructor(config) {
|
|
846
|
+
this.config = {
|
|
847
|
+
baseUrl: "https://sandbox.hyperswitch.io",
|
|
848
|
+
...config
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
async createPaymentIntent(amountCents, currency, customerId, metadata = {}) {
|
|
852
|
+
if (!this.config.apiKey) {
|
|
853
|
+
return {
|
|
854
|
+
paymentId: `hyp_mock_${Date.now()}`,
|
|
855
|
+
status: "succeeded",
|
|
856
|
+
amount: amountCents,
|
|
857
|
+
currency,
|
|
858
|
+
clientSecret: "mock_secret"
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
try {
|
|
862
|
+
const res = await fetch(`${this.config.baseUrl}/payments`, {
|
|
863
|
+
method: "POST",
|
|
864
|
+
headers: {
|
|
865
|
+
"Content-Type": "application/json",
|
|
866
|
+
"api-key": this.config.apiKey
|
|
867
|
+
},
|
|
868
|
+
body: JSON.stringify({
|
|
869
|
+
amount: amountCents,
|
|
870
|
+
currency: currency.toUpperCase(),
|
|
871
|
+
customer_id: customerId,
|
|
872
|
+
metadata
|
|
873
|
+
})
|
|
874
|
+
});
|
|
875
|
+
if (!res.ok)
|
|
876
|
+
throw new Error(`Hyperswitch HTTP ${res.status}`);
|
|
877
|
+
return await res.json();
|
|
878
|
+
} catch (err) {
|
|
879
|
+
return {
|
|
880
|
+
error: err.message,
|
|
881
|
+
paymentId: `hyp_fallback_${Date.now()}`,
|
|
882
|
+
status: "requires_payment_method"
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/site-auditor.ts
|
|
889
|
+
class SiteAuditor {
|
|
890
|
+
static inspectMeta(params) {
|
|
891
|
+
const checks = [];
|
|
892
|
+
const suggestions = [];
|
|
893
|
+
let score = 0;
|
|
894
|
+
const titleLen = (params.title || "").length;
|
|
895
|
+
if (titleLen >= 40 && titleLen <= 70) {
|
|
896
|
+
checks.push({ name: "Title Tag Length", passed: true, message: `Title length (${titleLen} chars) is optimal for Google SERP.`, weight: 25 });
|
|
897
|
+
score += 25;
|
|
898
|
+
} else {
|
|
899
|
+
checks.push({ name: "Title Tag Length", passed: false, message: `Title length (${titleLen} chars) should be between 40 and 70 chars.`, weight: 25 });
|
|
900
|
+
suggestions.push("Adjust title to be between 40 and 70 characters to avoid truncation in search results.");
|
|
901
|
+
}
|
|
902
|
+
const descLen = (params.description || "").length;
|
|
903
|
+
if (descLen >= 110 && descLen <= 170) {
|
|
904
|
+
checks.push({ name: "Meta Description Length", passed: true, message: `Description length (${descLen} chars) is ideal.`, weight: 25 });
|
|
905
|
+
score += 25;
|
|
906
|
+
} else {
|
|
907
|
+
checks.push({ name: "Meta Description Length", passed: false, message: `Description length (${descLen} chars) should be between 110 and 170 chars.`, weight: 25 });
|
|
908
|
+
suggestions.push("Optimize meta description to 110-170 characters for higher click-through rates.");
|
|
909
|
+
}
|
|
910
|
+
if (params.h1 && params.h1.trim().length > 5) {
|
|
911
|
+
checks.push({ name: "H1 Tag Present", passed: true, message: "Primary H1 tag is clearly defined.", weight: 25 });
|
|
912
|
+
score += 25;
|
|
913
|
+
} else {
|
|
914
|
+
checks.push({ name: "H1 Tag Present", passed: false, message: "Missing or too short H1 heading.", weight: 25 });
|
|
915
|
+
suggestions.push("Ensure your page has a clear, keyword-rich H1 heading.");
|
|
916
|
+
}
|
|
917
|
+
if (params.directAnswer && params.directAnswer.trim().length > 30) {
|
|
918
|
+
checks.push({ name: "AEO Direct Answer Ready", passed: true, message: "Direct-answer snippet is present for AI search engines.", weight: 25 });
|
|
919
|
+
score += 25;
|
|
920
|
+
} else {
|
|
921
|
+
checks.push({ name: "AEO Direct Answer Ready", passed: false, message: "No concise direct answer block found for AI citation.", weight: 25 });
|
|
922
|
+
suggestions.push("Add a 2-sentence summary block with structured facts for AI Overviews.");
|
|
923
|
+
}
|
|
924
|
+
const status = score >= 90 ? "excellent" : score >= 75 ? "good" : score >= 50 ? "needs_improvement" : "poor";
|
|
925
|
+
return {
|
|
926
|
+
score,
|
|
927
|
+
status,
|
|
928
|
+
checks,
|
|
929
|
+
suggestions
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/schema-builder.ts
|
|
935
|
+
class SchemaGraphBuilder {
|
|
936
|
+
static buildGraph(opts) {
|
|
937
|
+
const graph = [
|
|
938
|
+
{
|
|
939
|
+
"@type": "Product",
|
|
940
|
+
"@id": `${opts.url}#product`,
|
|
941
|
+
name: opts.name,
|
|
942
|
+
description: opts.description,
|
|
943
|
+
inLanguage: opts.locale || "fr",
|
|
944
|
+
image: opts.image || `${opts.url}/api/og`,
|
|
945
|
+
brand: {
|
|
946
|
+
"@type": "Brand",
|
|
947
|
+
name: opts.brandName
|
|
948
|
+
},
|
|
949
|
+
aggregateRating: {
|
|
950
|
+
"@type": "AggregateRating",
|
|
951
|
+
ratingValue: opts.ratingValue || "4.9",
|
|
952
|
+
reviewCount: opts.reviewCount || "1280",
|
|
953
|
+
bestRating: "5",
|
|
954
|
+
worstRating: "1"
|
|
955
|
+
},
|
|
956
|
+
offers: {
|
|
957
|
+
"@type": "Offer",
|
|
958
|
+
price: (opts.price || 49).toString(),
|
|
959
|
+
priceCurrency: opts.currency || "EUR",
|
|
960
|
+
availability: "https://schema.org/InStock",
|
|
961
|
+
url: opts.url
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
];
|
|
965
|
+
if (opts.faqs && opts.faqs.length > 0) {
|
|
966
|
+
graph.push({
|
|
967
|
+
"@type": "FAQPage",
|
|
968
|
+
"@id": `${opts.url}#faq`,
|
|
969
|
+
inLanguage: opts.locale || "fr",
|
|
970
|
+
mainEntity: opts.faqs.map((f) => ({
|
|
971
|
+
"@type": "Question",
|
|
972
|
+
name: f.question,
|
|
973
|
+
acceptedAnswer: {
|
|
974
|
+
"@type": "Answer",
|
|
975
|
+
text: f.answer
|
|
976
|
+
}
|
|
977
|
+
}))
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
graph.push({
|
|
981
|
+
"@type": "WebPage",
|
|
982
|
+
"@id": `${opts.url}#webpage`,
|
|
983
|
+
url: opts.url,
|
|
984
|
+
name: opts.name,
|
|
985
|
+
description: opts.description,
|
|
986
|
+
speakable: {
|
|
987
|
+
"@type": "SpeakableSpecification",
|
|
988
|
+
cssSelector: [".geo-direct-answer", "h1"]
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
return {
|
|
992
|
+
"@context": "https://schema.org",
|
|
993
|
+
"@graph": graph
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// src/engine.ts
|
|
999
|
+
class LynxSeoEngine {
|
|
1000
|
+
config;
|
|
1001
|
+
servicesMap;
|
|
1002
|
+
authStatus;
|
|
1003
|
+
analytics;
|
|
1004
|
+
serp;
|
|
1005
|
+
backlinks;
|
|
1006
|
+
ai;
|
|
1007
|
+
tokenMeter;
|
|
1008
|
+
auditor = SiteAuditor;
|
|
1009
|
+
schema = SchemaGraphBuilder;
|
|
1010
|
+
indexNow = IndexNowClient;
|
|
1011
|
+
constructor(config) {
|
|
1012
|
+
this.config = {
|
|
1013
|
+
currency: "EUR",
|
|
1014
|
+
currencySymbol: "€",
|
|
1015
|
+
defaultLocale: "fr",
|
|
1016
|
+
supportedLocales: ["fr", "en", "es", "de", "ar"],
|
|
1017
|
+
...config
|
|
1018
|
+
};
|
|
1019
|
+
this.servicesMap = new Map((config.services || []).map((s) => [s.slug, s]));
|
|
1020
|
+
const rawKey = config.apiKey || config.licenseKey || "";
|
|
1021
|
+
this.authStatus = ApiKeyGuardian.validate(rawKey, config.domain);
|
|
1022
|
+
this.analytics = new LynxAnalyticsClient(rawKey);
|
|
1023
|
+
this.serp = new SerpClient(rawKey);
|
|
1024
|
+
this.backlinks = new BacklinksClient(rawKey);
|
|
1025
|
+
this.ai = new AiCopilotClient(rawKey);
|
|
1026
|
+
this.tokenMeter = new LagoTokenMeter;
|
|
1027
|
+
}
|
|
1028
|
+
async submitToIndexNow(urls, indexNowKey = this.config.apiKey || this.config.licenseKey || "") {
|
|
1029
|
+
const host = this.config.domain.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
1030
|
+
return IndexNowClient.submitUrls({
|
|
1031
|
+
host,
|
|
1032
|
+
key: indexNowKey,
|
|
1033
|
+
urlList: urls
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
resolve(input) {
|
|
1037
|
+
const t0 = performance.now();
|
|
1038
|
+
if (!this.authStatus.isValid) {
|
|
1039
|
+
console.warn(`[@lynxflow/seo-engine] Authentication / Domain Limit Error: ${this.authStatus.errorMessage}`);
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
const rawKey = this.config.apiKey || this.config.licenseKey || "";
|
|
1043
|
+
const targetPath = typeof input === "string" ? input.replace(/^\/+/, "").replace(/\/+$/, "") : `${input.type || "solutions"}/${input.slug || ""}`;
|
|
1044
|
+
const quota = ApiKeyGuardian.trackAndCheckUniquePage(rawKey, targetPath, this.authStatus.maxPages);
|
|
1045
|
+
if (!quota.allowed) {
|
|
1046
|
+
console.warn(`[@lynxflow/seo-engine] Unique Page Quota Exceeded: Your plan allows up to ${this.authStatus.maxPages.toLocaleString()} unique pages (Current active catalog: ${quota.uniqueCount}). Please upgrade to a higher plan.`);
|
|
1047
|
+
return null;
|
|
1048
|
+
}
|
|
1049
|
+
const domain = this.config.domain.replace(/\/$/, "");
|
|
1050
|
+
const defaultService = this.config.services?.[0] || {
|
|
1051
|
+
slug: "crm-pipeline",
|
|
1052
|
+
name: "CRM Pipeline Commercial",
|
|
1053
|
+
pricePerMonth: 49,
|
|
1054
|
+
category: "Automatisation Commerciale",
|
|
1055
|
+
features: ["Pipeline Kanban", "Relances IA 24/7", "WhatsApp Sync"],
|
|
1056
|
+
faqs: [
|
|
1057
|
+
{ question: "Combien de temps prend la mise en place ?", answer: "Déploiement immédiat en moins de 10 minutes." },
|
|
1058
|
+
{ question: "Est-ce sans engagement ?", answer: "Oui, vous pouvez résilier à tout moment sans frais." }
|
|
1059
|
+
]
|
|
1060
|
+
};
|
|
1061
|
+
if (typeof input === "object") {
|
|
1062
|
+
const locale2 = (input.locale || input.language || this.config.defaultLocale || "fr").toLowerCase();
|
|
1063
|
+
const type = input.type || (input.competitor ? "comparison" : input.city ? "geo" : "industry");
|
|
1064
|
+
const service = (input.slug ? this.servicesMap.get(input.slug) : null) || defaultService;
|
|
1065
|
+
if (type === "comparison" && input.competitor) {
|
|
1066
|
+
return this.buildComparisonPage(service, input.competitor, domain, t0, locale2);
|
|
1067
|
+
}
|
|
1068
|
+
if (type === "industry" && input.industry) {
|
|
1069
|
+
return this.buildIndustryPage(service, input.industry, domain, t0, locale2);
|
|
1070
|
+
}
|
|
1071
|
+
if (type === "role" && input.role) {
|
|
1072
|
+
return this.buildRolePage(service, input.role, domain, t0, locale2);
|
|
1073
|
+
}
|
|
1074
|
+
const country = (input.country || locale2.toUpperCase() || "FR").toUpperCase();
|
|
1075
|
+
const city = input.city || "Paris";
|
|
1076
|
+
return this.buildGeoPage(service, country, city, domain, t0, locale2);
|
|
1077
|
+
}
|
|
1078
|
+
const cleanPath = input.toLowerCase().replace(/^\//, "").replace(/\/$/, "");
|
|
1079
|
+
const parts = cleanPath.split("/");
|
|
1080
|
+
let locale = this.config.defaultLocale || "fr";
|
|
1081
|
+
let pathParts = parts;
|
|
1082
|
+
if (parts.length > 1 && isSupportedLanguage(parts[0])) {
|
|
1083
|
+
locale = parts[0];
|
|
1084
|
+
pathParts = parts.slice(1);
|
|
1085
|
+
}
|
|
1086
|
+
if (pathParts[0] === "solutions") {
|
|
1087
|
+
if (pathParts.length >= 5) {
|
|
1088
|
+
const service = this.servicesMap.get(pathParts[1]) || defaultService;
|
|
1089
|
+
const industry = (pathParts[2] || "").replace(/-/g, " ");
|
|
1090
|
+
const competitor = (pathParts[3] || "").replace(/-/g, " ");
|
|
1091
|
+
const software = (pathParts[4] || "").replace(/-/g, " ");
|
|
1092
|
+
const role = (pathParts[5] || "").replace(/-/g, " ");
|
|
1093
|
+
const city = (pathParts[6] || "Paris").replace(/-/g, " ");
|
|
1094
|
+
const tool = (pathParts[7] || "").replace(/-/g, " ");
|
|
1095
|
+
return this.buildHyperCombinedPage({
|
|
1096
|
+
service,
|
|
1097
|
+
industry,
|
|
1098
|
+
competitor,
|
|
1099
|
+
software,
|
|
1100
|
+
role,
|
|
1101
|
+
city,
|
|
1102
|
+
tool,
|
|
1103
|
+
domain,
|
|
1104
|
+
t0,
|
|
1105
|
+
locale,
|
|
1106
|
+
path: cleanPath
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
if (pathParts.length >= 4) {
|
|
1110
|
+
const service = this.servicesMap.get(pathParts[1]) || defaultService;
|
|
1111
|
+
const country = pathParts[2].toUpperCase();
|
|
1112
|
+
const cityName = pathParts[3].charAt(0).toUpperCase() + pathParts[3].slice(1);
|
|
1113
|
+
return this.buildGeoPage(service, country, cityName, domain, t0, locale, cleanPath);
|
|
1114
|
+
}
|
|
1115
|
+
if (pathParts.length >= 3) {
|
|
1116
|
+
const service = this.servicesMap.get(pathParts[1]) || defaultService;
|
|
1117
|
+
const defaultCountry = locale.toUpperCase();
|
|
1118
|
+
const cityName = pathParts[2].charAt(0).toUpperCase() + pathParts[2].slice(1);
|
|
1119
|
+
return this.buildGeoPage(service, defaultCountry, cityName, domain, t0, locale, cleanPath);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const detectServiceFromSlug = (slug) => {
|
|
1123
|
+
for (const [sSlug, sDef] of this.servicesMap.entries()) {
|
|
1124
|
+
if (slug.startsWith(`${sSlug}-`)) {
|
|
1125
|
+
return { service: sDef, remainder: slug.substring(sSlug.length + 1) };
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return { service: defaultService, remainder: slug };
|
|
1129
|
+
};
|
|
1130
|
+
if ((pathParts[0] === "comparatif" || pathParts[0] === "comparison" || pathParts[0] === "vs") && pathParts.length >= 2) {
|
|
1131
|
+
const raw = pathParts[1];
|
|
1132
|
+
const vsMatch = raw.match(/^(.*?)-vs-(.*?)$/);
|
|
1133
|
+
let targetService = defaultService;
|
|
1134
|
+
let competitor = raw;
|
|
1135
|
+
if (vsMatch) {
|
|
1136
|
+
targetService = this.servicesMap.get(vsMatch[1]) || defaultService;
|
|
1137
|
+
competitor = vsMatch[2];
|
|
1138
|
+
} else {
|
|
1139
|
+
const detected = detectServiceFromSlug(raw);
|
|
1140
|
+
targetService = detected.service;
|
|
1141
|
+
competitor = detected.remainder;
|
|
1142
|
+
}
|
|
1143
|
+
return this.buildComparisonPage(targetService, competitor.replace(/-/g, " "), domain, t0, locale, cleanPath);
|
|
1144
|
+
}
|
|
1145
|
+
if (pathParts[0] === "alternatives" && pathParts.length >= 2) {
|
|
1146
|
+
const raw = pathParts[1].replace(/^alternative-(a-|to-)?/i, "");
|
|
1147
|
+
const { service, remainder } = detectServiceFromSlug(raw);
|
|
1148
|
+
return this.buildAlternativePage(service, remainder.replace(/-/g, " "), domain, t0, locale, cleanPath);
|
|
1149
|
+
}
|
|
1150
|
+
if ((pathParts[0] === "secteurs" || pathParts[0] === "industries") && pathParts.length >= 2) {
|
|
1151
|
+
const raw = pathParts[1].replace(/-(pour|for)-/i, "-");
|
|
1152
|
+
const { service, remainder } = detectServiceFromSlug(raw);
|
|
1153
|
+
return this.buildIndustryPage(service, remainder.replace(/-/g, " "), domain, t0, locale, cleanPath);
|
|
1154
|
+
}
|
|
1155
|
+
if (pathParts[0] === "integrations" && pathParts.length >= 2) {
|
|
1156
|
+
const raw = pathParts[1].replace(/-(avec|with)-/i, "-");
|
|
1157
|
+
const { service, remainder } = detectServiceFromSlug(raw);
|
|
1158
|
+
return this.buildIntegrationPage(service, remainder.replace(/-/g, " "), domain, t0, locale, cleanPath);
|
|
1159
|
+
}
|
|
1160
|
+
if ((pathParts[0] === "metiers" || pathParts[0] === "roles") && pathParts.length >= 2) {
|
|
1161
|
+
const raw = pathParts[1].replace(/-(pour|for)-/i, "-");
|
|
1162
|
+
const { service, remainder } = detectServiceFromSlug(raw);
|
|
1163
|
+
return this.buildRolePage(service, remainder.replace(/-/g, " "), domain, t0, locale, cleanPath);
|
|
1164
|
+
}
|
|
1165
|
+
if ((pathParts[0] === "cas-usage" || pathParts[0] === "use-cases") && pathParts.length >= 2) {
|
|
1166
|
+
const { service, remainder } = detectServiceFromSlug(pathParts[1]);
|
|
1167
|
+
return this.buildGeoPage(service, "FR", remainder.replace(/-/g, " "), domain, t0, locale, cleanPath);
|
|
1168
|
+
}
|
|
1169
|
+
if ((pathParts[0] === "outils" || pathParts[0] === "tools") && pathParts.length >= 2) {
|
|
1170
|
+
const { service } = detectServiceFromSlug(pathParts[1]);
|
|
1171
|
+
return this.buildGeoPage(service, "FR", "Calculateur ROI", domain, t0, locale, cleanPath);
|
|
1172
|
+
}
|
|
1173
|
+
return this.buildGeoPage(defaultService, "FR", "Paris", domain, t0, locale, cleanPath);
|
|
1174
|
+
}
|
|
1175
|
+
buildHyperCombinedPage(opts) {
|
|
1176
|
+
const { service, industry, competitor, software, role, city, tool, domain, t0, locale, path } = opts;
|
|
1177
|
+
const currentYear = new Date().getFullYear();
|
|
1178
|
+
const indStr = industry ? ` pour ${industry}` : "";
|
|
1179
|
+
const compStr = competitor ? ` (Alternative à ${competitor})` : "";
|
|
1180
|
+
const softStr = software ? ` connecté avec ${software}` : "";
|
|
1181
|
+
const roleStr = role ? ` pour ${role}` : "";
|
|
1182
|
+
const cityStr = city ? ` à ${city}` : "";
|
|
1183
|
+
const toolStr = tool ? ` | Simulateur ROI` : "";
|
|
1184
|
+
const title = `${service.name}${indStr}${roleStr}${cityStr}${compStr}${toolStr} — ${this.config.brandName}`;
|
|
1185
|
+
const description = `Découvrez la solution de ${service.name}${indStr}${cityStr}. Automatisez vos flux, gagnez 10h/semaine${softStr}. Déploiement en 10 minutes. Note 4.9/5★.`;
|
|
1186
|
+
const h1 = `${service.name}${indStr}${cityStr}`;
|
|
1187
|
+
const directAnswer = `${this.config.brandName} propose le meilleur ${service.name}${indStr}${roleStr}${cityStr}${compStr}${softStr}. Mise en service immédiate dès ${service.pricePerMonth} ${this.config.currencySymbol || "€"}/mois sans engagement.`;
|
|
1188
|
+
return this.assemblePage({
|
|
1189
|
+
url: `${domain}/${path}`,
|
|
1190
|
+
title,
|
|
1191
|
+
description,
|
|
1192
|
+
h1,
|
|
1193
|
+
directAnswer,
|
|
1194
|
+
service,
|
|
1195
|
+
locale,
|
|
1196
|
+
path,
|
|
1197
|
+
t0
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
buildGeoPage(service, country, city, domain, t0, locale = "fr", path = `solutions/${service.slug}/${country.toLowerCase()}/${city.toLowerCase()}`) {
|
|
1201
|
+
const dict = getDictionary(locale);
|
|
1202
|
+
const title = dict.geoTitle(service.name, city, country, this.config.brandName);
|
|
1203
|
+
const description = dict.geoDesc(service.name, city, this.config.brandName);
|
|
1204
|
+
const h1 = `${service.name} à ${city}`;
|
|
1205
|
+
const directAnswer = dict.geoDirectAnswer(this.config.brandName, service.name, city, country, service.pricePerMonth, this.config.currencySymbol || "€");
|
|
1206
|
+
return this.assemblePage({
|
|
1207
|
+
url: `${domain}/${path}`,
|
|
1208
|
+
title,
|
|
1209
|
+
description,
|
|
1210
|
+
h1,
|
|
1211
|
+
directAnswer,
|
|
1212
|
+
service,
|
|
1213
|
+
locale,
|
|
1214
|
+
path,
|
|
1215
|
+
t0
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
buildComparisonPage(service, competitor, domain, t0, locale = "fr", path = `comparatif/${competitor.toLowerCase().replace(/\s+/g, "-")}`) {
|
|
1219
|
+
const dict = getDictionary(locale);
|
|
1220
|
+
const compName = competitor.charAt(0).toUpperCase() + competitor.slice(1);
|
|
1221
|
+
const currentYear = new Date().getFullYear();
|
|
1222
|
+
const title = `${service.name} : ${this.config.brandName} vs ${compName} (${currentYear})`;
|
|
1223
|
+
const description = dict.vsDesc(this.config.brandName, compName);
|
|
1224
|
+
const h1 = `${service.name} : ${this.config.brandName} vs ${compName}`;
|
|
1225
|
+
const directAnswer = dict.vsDirectAnswer(this.config.brandName, compName);
|
|
1226
|
+
return this.assemblePage({
|
|
1227
|
+
url: `${domain}/${path}`,
|
|
1228
|
+
title,
|
|
1229
|
+
description,
|
|
1230
|
+
h1,
|
|
1231
|
+
directAnswer,
|
|
1232
|
+
service,
|
|
1233
|
+
locale,
|
|
1234
|
+
path,
|
|
1235
|
+
t0
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
buildAlternativePage(service, competitor, domain, t0, locale = "fr", path = `alternatives/alternative-a-${competitor.toLowerCase().replace(/\s+/g, "-")}`) {
|
|
1239
|
+
const dict = getDictionary(locale);
|
|
1240
|
+
const compName = competitor.charAt(0).toUpperCase() + competitor.slice(1);
|
|
1241
|
+
const currentYear = new Date().getFullYear();
|
|
1242
|
+
const title = `Meilleure Alternative à ${compName} (${service.name}) en ${currentYear} — ${this.config.brandName}`;
|
|
1243
|
+
const description = dict.altDesc(compName, this.config.brandName);
|
|
1244
|
+
const h1 = `Alternative à ${compName} pour votre ${service.name}`;
|
|
1245
|
+
const directAnswer = dict.altDirectAnswer(this.config.brandName, compName);
|
|
1246
|
+
return this.assemblePage({
|
|
1247
|
+
url: `${domain}/${path}`,
|
|
1248
|
+
title,
|
|
1249
|
+
description,
|
|
1250
|
+
h1,
|
|
1251
|
+
directAnswer,
|
|
1252
|
+
service,
|
|
1253
|
+
locale,
|
|
1254
|
+
path,
|
|
1255
|
+
t0
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
buildIndustryPage(service, industry, domain, t0, locale = "fr", path = `secteurs/${industry.toLowerCase().replace(/\s+/g, "-")}`) {
|
|
1259
|
+
const dict = getDictionary(locale);
|
|
1260
|
+
const indName = industry.charAt(0).toUpperCase() + industry.slice(1);
|
|
1261
|
+
const title = dict.industryTitle(service.name, indName, this.config.brandName);
|
|
1262
|
+
const description = dict.industryDesc(service.name, indName);
|
|
1263
|
+
const h1 = `${service.name} pour ${indName}`;
|
|
1264
|
+
const directAnswer = `${this.config.brandName} propose une solution de ${service.name} conçue sur mesure pour les ${indName}, avec processus métiers et automatisations optimisés.`;
|
|
1265
|
+
return this.assemblePage({
|
|
1266
|
+
url: `${domain}/${path}`,
|
|
1267
|
+
title,
|
|
1268
|
+
description,
|
|
1269
|
+
h1,
|
|
1270
|
+
directAnswer,
|
|
1271
|
+
service,
|
|
1272
|
+
locale,
|
|
1273
|
+
path,
|
|
1274
|
+
t0
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
buildRolePage(service, role, domain, t0, locale = "fr", path = `metiers/${role.toLowerCase().replace(/\s+/g, "-")}`) {
|
|
1278
|
+
const roleName = role.charAt(0).toUpperCase() + role.slice(1);
|
|
1279
|
+
const title = `${service.name} pour ${roleName} — ${this.config.brandName}`;
|
|
1280
|
+
const description = `Optimisez votre quotidien de ${roleName} grâce à notre solution de ${service.name}. Gagnez 10h par semaine.`;
|
|
1281
|
+
const h1 = `${service.name} pour ${roleName}`;
|
|
1282
|
+
const directAnswer = `En tant que ${roleName}, ${this.config.brandName} (${service.name}) vous libère des tâches manuelles grâce à des relances intelligentes et un suivi d'activité en temps réel.`;
|
|
1283
|
+
return this.assemblePage({
|
|
1284
|
+
url: `${domain}/${path}`,
|
|
1285
|
+
title,
|
|
1286
|
+
description,
|
|
1287
|
+
h1,
|
|
1288
|
+
directAnswer,
|
|
1289
|
+
service,
|
|
1290
|
+
locale,
|
|
1291
|
+
path,
|
|
1292
|
+
t0
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
buildIntegrationPage(service, software, domain, t0, locale = "fr", path = `integrations/${software.toLowerCase().replace(/\s+/g, "-")}`) {
|
|
1296
|
+
const softName = software.charAt(0).toUpperCase() + software.slice(1);
|
|
1297
|
+
const title = `Intégration ${softName} & ${service.name} — ${this.config.brandName}`;
|
|
1298
|
+
const description = `Connectez facilement votre compte ${softName} à notre ${service.name}. Synchronisation bidirectionnelle instantanée.`;
|
|
1299
|
+
const h1 = `Intégration ${softName} & ${service.name}`;
|
|
1300
|
+
const directAnswer = `L'intégration native entre ${softName} et ${this.config.brandName} (${service.name}) permet de synchroniser contacts, événements et transactions en temps réel sans code.`;
|
|
1301
|
+
return this.assemblePage({
|
|
1302
|
+
url: `${domain}/${path}`,
|
|
1303
|
+
title,
|
|
1304
|
+
description,
|
|
1305
|
+
h1,
|
|
1306
|
+
directAnswer,
|
|
1307
|
+
service,
|
|
1308
|
+
locale,
|
|
1309
|
+
path,
|
|
1310
|
+
t0
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
assemblePage(opts) {
|
|
1314
|
+
const dict = getDictionary(opts.locale);
|
|
1315
|
+
const domain = this.config.domain.replace(/\/$/, "");
|
|
1316
|
+
const supportedLocales = this.config.supportedLocales || ["fr", "en", "es", "de", "ar"];
|
|
1317
|
+
const hreflangs = supportedLocales.map((l) => ({
|
|
1318
|
+
lang: l,
|
|
1319
|
+
url: l === this.config.defaultLocale ? `${domain}/${opts.path}` : `${domain}/${l}/${opts.path}`
|
|
1320
|
+
}));
|
|
1321
|
+
const localizedFaqs = opts.service.faqs?.length ? opts.service.faqs : [
|
|
1322
|
+
{ question: dict.faqDeploymentQ, answer: dict.faqDeploymentA },
|
|
1323
|
+
{ question: dict.faqCommitmentQ, answer: dict.faqCommitmentA }
|
|
1324
|
+
];
|
|
1325
|
+
const ogImageUrl = `${domain}/api/og?title=${encodeURIComponent(opts.h1)}&brand=${encodeURIComponent(this.config.brandName)}&service=${encodeURIComponent(opts.service.name)}`;
|
|
1326
|
+
const jsonLd = {
|
|
1327
|
+
"@context": "https://schema.org",
|
|
1328
|
+
"@graph": [
|
|
1329
|
+
{
|
|
1330
|
+
"@type": "Product",
|
|
1331
|
+
"@id": `${opts.url}#product`,
|
|
1332
|
+
name: opts.title,
|
|
1333
|
+
description: opts.description,
|
|
1334
|
+
inLanguage: opts.locale,
|
|
1335
|
+
image: ogImageUrl,
|
|
1336
|
+
brand: { "@type": "Brand", name: this.config.brandName },
|
|
1337
|
+
aggregateRating: {
|
|
1338
|
+
"@type": "AggregateRating",
|
|
1339
|
+
ratingValue: "4.9",
|
|
1340
|
+
reviewCount: "1280",
|
|
1341
|
+
bestRating: "5",
|
|
1342
|
+
worstRating: "1"
|
|
1343
|
+
},
|
|
1344
|
+
offers: {
|
|
1345
|
+
"@type": "Offer",
|
|
1346
|
+
price: opts.service.pricePerMonth.toString(),
|
|
1347
|
+
priceCurrency: this.config.currency,
|
|
1348
|
+
availability: "https://schema.org/InStock",
|
|
1349
|
+
url: opts.url
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
"@type": "FAQPage",
|
|
1354
|
+
"@id": `${opts.url}#faq`,
|
|
1355
|
+
inLanguage: opts.locale,
|
|
1356
|
+
mainEntity: localizedFaqs.map((faq) => ({
|
|
1357
|
+
"@type": "Question",
|
|
1358
|
+
name: faq.question,
|
|
1359
|
+
acceptedAnswer: {
|
|
1360
|
+
"@type": "Answer",
|
|
1361
|
+
text: faq.answer
|
|
1362
|
+
}
|
|
1363
|
+
}))
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
"@type": "WebPage",
|
|
1367
|
+
"@id": `${opts.url}#webpage`,
|
|
1368
|
+
url: opts.url,
|
|
1369
|
+
name: opts.title,
|
|
1370
|
+
description: opts.description,
|
|
1371
|
+
speakable: {
|
|
1372
|
+
"@type": "SpeakableSpecification",
|
|
1373
|
+
cssSelector: [".geo-direct-answer", "h1"]
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
]
|
|
1377
|
+
};
|
|
1378
|
+
const directAnswerHtml = `<div class="geo-direct-answer" data-geo-extract="true"><p>${opts.directAnswer}</p></div>`;
|
|
1379
|
+
return {
|
|
1380
|
+
url: opts.url,
|
|
1381
|
+
title: opts.title,
|
|
1382
|
+
description: opts.description,
|
|
1383
|
+
h1: opts.h1,
|
|
1384
|
+
directAnswer: opts.directAnswer,
|
|
1385
|
+
service: opts.service,
|
|
1386
|
+
locale: opts.locale,
|
|
1387
|
+
hreflangs,
|
|
1388
|
+
faqs: localizedFaqs,
|
|
1389
|
+
jsonLd,
|
|
1390
|
+
htmlBody: `<h1>${opts.h1}</h1><p>${opts.description}</p>${directAnswerHtml}`,
|
|
1391
|
+
markdownBody: `# ${opts.h1}
|
|
1392
|
+
|
|
1393
|
+
${opts.description}
|
|
1394
|
+
|
|
1395
|
+
> ${opts.directAnswer}`,
|
|
1396
|
+
executionTimeMs: parseFloat((performance.now() - opts.t0).toFixed(3)),
|
|
1397
|
+
fullUrl: opts.url,
|
|
1398
|
+
meta: {
|
|
1399
|
+
title: opts.title,
|
|
1400
|
+
description: opts.description,
|
|
1401
|
+
h1: opts.h1,
|
|
1402
|
+
canonical: opts.url
|
|
1403
|
+
},
|
|
1404
|
+
content: {
|
|
1405
|
+
directAnswerGeoHtml: directAnswerHtml,
|
|
1406
|
+
heroHeadline: opts.h1,
|
|
1407
|
+
heroSubheadline: opts.description,
|
|
1408
|
+
markdownBody: `# ${opts.h1}
|
|
1409
|
+
|
|
1410
|
+
${opts.description}
|
|
1411
|
+
|
|
1412
|
+
> ${opts.directAnswer}`,
|
|
1413
|
+
faqList: localizedFaqs
|
|
1414
|
+
},
|
|
1415
|
+
schemaJsonLd: jsonLd,
|
|
1416
|
+
pricing: {
|
|
1417
|
+
priceNumber: opts.service.pricePerMonth,
|
|
1418
|
+
priceFormatted: `${opts.service.pricePerMonth} ${this.config.currencySymbol}`,
|
|
1419
|
+
currency: this.config.currency || "EUR"
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
getUniquePageCount() {
|
|
1424
|
+
const rawKey = this.config.apiKey || this.config.licenseKey || "";
|
|
1425
|
+
return ApiKeyGuardian.getUniquePageCount(rawKey);
|
|
1426
|
+
}
|
|
1427
|
+
getUniquePageList() {
|
|
1428
|
+
const rawKey = this.config.apiKey || this.config.licenseKey || "";
|
|
1429
|
+
return ApiKeyGuardian.getUniquePageList(rawKey);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
// src/index.ts
|
|
1433
|
+
function createLynxSeoEngine(config) {
|
|
1434
|
+
return new LynxSeoEngine(config);
|
|
1435
|
+
}
|
|
1436
|
+
var LynxSeo = {
|
|
1437
|
+
createEngine: createLynxSeoEngine,
|
|
1438
|
+
validateApiKey: ApiKeyGuardian.validate,
|
|
1439
|
+
createTokenManager: (tier) => new TokenQuotaManager(tier),
|
|
1440
|
+
createLagoMeter: (apiKey, lagoUrl) => new LagoTokenMeter(apiKey, lagoUrl),
|
|
1441
|
+
createHyperswitchGateway: (apiKey, baseUrl) => new HyperswitchGateway({ apiKey, baseUrl }),
|
|
1442
|
+
submitToIndexNow: IndexNowClient.submitUrls,
|
|
1443
|
+
inspectMeta: SiteAuditor.inspectMeta,
|
|
1444
|
+
buildSchemaGraph: SchemaGraphBuilder.buildGraph
|
|
1445
|
+
};
|
|
1446
|
+
var src_default = LynxSeo;
|
|
1447
|
+
export {
|
|
1448
|
+
src_default as default,
|
|
1449
|
+
createLynxSeoEngine,
|
|
1450
|
+
TokenQuotaManager,
|
|
1451
|
+
SiteAuditor,
|
|
1452
|
+
SerpClient,
|
|
1453
|
+
SchemaGraphBuilder,
|
|
1454
|
+
LynxSeoEngine,
|
|
1455
|
+
LynxSeo,
|
|
1456
|
+
LynxAnalyticsClient,
|
|
1457
|
+
LagoTokenMeter,
|
|
1458
|
+
IndexNowClient,
|
|
1459
|
+
HyperswitchGateway,
|
|
1460
|
+
BacklinksClient,
|
|
1461
|
+
ApiKeyGuardian,
|
|
1462
|
+
AiCopilotClient
|
|
1463
|
+
};
|