@lynxflow/seo-engine 1.8.1 → 1.8.3

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.
@@ -0,0 +1,369 @@
1
+ /**
2
+ * 🧠 Universal Open Multilingual Power Words & Click Psychology Engine
3
+ * Supports 50+ World Languages (FR, EN, ES, DE, IT, NL, PT, AR, RU, ZH, JA, HI, PL, SV, TR, KO, etc.)
4
+ * With Dynamic Runtime Dictionary Registration for Infinite Language Extensibility.
5
+ */
6
+
7
+ export type LanguageCode = string;
8
+
9
+ export interface CategoryWordBank {
10
+ authority: string[];
11
+ urgency: string[];
12
+ value: string[];
13
+ curiosity: string[];
14
+ sentimentPositive: string[];
15
+ sentimentNegative: string[];
16
+ }
17
+
18
+ export interface PsychologyAnalysisResult {
19
+ score: number; // 0 to 100
20
+ powerWordsDetected: string[];
21
+ sentiment: "positif" | "negatif" | "neutre";
22
+ hasNumber: boolean;
23
+ hasYear: boolean;
24
+ hasCuriosityTrigger: boolean;
25
+ hasUrgencyTrigger: boolean;
26
+ hasAuthorityTrigger: boolean;
27
+ hasCurrencyOrDiscount: boolean;
28
+ hasBracketsOrParentheses: boolean;
29
+ recommendations: string[];
30
+ suggestedTitles: string[];
31
+ }
32
+
33
+ // Pre-loaded high-converting dictionaries across global languages
34
+ export const MULTILINGUAL_POWER_WORDS: Record<string, CategoryWordBank> = {
35
+ fr: {
36
+ authority: [
37
+ "officiel", "guide", "expert", "complet", "certifié", "ultime", "meilleur", "meilleurs", "comparatif",
38
+ "maîtriser", "spécialiste", "leader", "professionnel", "authentique", "infaillible", "reconnu", "démontré", "avis"
39
+ ],
40
+ urgency: [
41
+ "rapide", "instantané", "maintenant", "immédiat", "express", "2026", "dernière minute", "urgent",
42
+ "dépêchez-vous", "en 5 min", "en 10 min", "aujourd'hui", "débloquer", "vite"
43
+ ],
44
+ value: [
45
+ "gratuit", "économique", "rentable", "offert", "sans engagement", "garanti", "facile", "clé en main",
46
+ "rentabilité", "gain", "économisez", "inclus", "accessible", "inédit", "remise", "promo"
47
+ ],
48
+ curiosity: [
49
+ "exclusif", "secret", "astuces", "révélé", "inédit", "top", "méthode", "pourquoi", "comment",
50
+ "découvrir", "choc", "incontournable", "mystère", "incroyable", "vérité"
51
+ ],
52
+ sentimentPositive: [
53
+ "parfait", "incroyable", "magnifique", "puissant", "réussi", "génial", "exceptionnel", "facile",
54
+ "meilleur", "succès", "brillant", "splendide", "radieux", "magique", "sublime"
55
+ ],
56
+ sentimentNegative: [
57
+ "erreur", "piège", "danger", "éviter", "pire", "arnaque", "catastrophe", "risqué", "menace", "perte", "alerte"
58
+ ]
59
+ },
60
+ en: {
61
+ authority: [
62
+ "ultimate", "complete", "definitive", "guide", "expert", "official", "proven", "master", "certified",
63
+ "best", "top", "authority", "case study", "benchmark", "professional", "review", "formula"
64
+ ],
65
+ urgency: [
66
+ "instant", "fast", "quick", "now", "today", "2026", "deadline", "last chance", "step-by-step",
67
+ "in 5 minutes", "immediate", "urgent", "unlock", "rapid"
68
+ ],
69
+ value: [
70
+ "free", "affordable", "save", "guaranteed", "easy", "bonus", "discount", "effortless",
71
+ "lifetime", "roi", "value", "exclusive deal", "profitable"
72
+ ],
73
+ curiosity: [
74
+ "secret", "revealed", "insider", "hacks", "why", "how to", "untold", "surprising",
75
+ "shocking", "hidden", "must-know", "unbelievable", "myth"
76
+ ],
77
+ sentimentPositive: [
78
+ "amazing", "brilliant", "powerful", "epic", "perfect", "stunning", "thriving", "seamless",
79
+ "exceptional", "masterpiece", "unbeatable"
80
+ ],
81
+ sentimentNegative: [
82
+ "mistake", "warning", "fail", "avoid", "risk", "trap", "worst", "scam", "danger", "flaw"
83
+ ]
84
+ },
85
+ es: {
86
+ authority: [
87
+ "definitiva", "completa", "guía", "experto", "oficial", "certificado", "mejor", "mejores", "comparativa",
88
+ "profesional", "comprobado", "líder", "análisis"
89
+ ],
90
+ urgency: [
91
+ "rápido", "ahora", "urgente", "hoy", "2026", "en 5 minutos", "inmediato", "última oportunidad", "fácil"
92
+ ],
93
+ value: [
94
+ "gratis", "económico", "ahorra", "garantizado", "rentable", "oferta", "descuento", "exclusivo"
95
+ ],
96
+ curiosity: [
97
+ "secreto", "revelado", "trucos", "por qué", "cómo", "impactante", "increíble", "descubre", "método"
98
+ ],
99
+ sentimentPositive: [
100
+ "excelente", "increíble", "perfecto", "potente", "maravilloso", "éxito", "fantástico"
101
+ ],
102
+ sentimentNegative: [
103
+ "error", "peligro", "evita", "peor", "estafa", "trampa", "fracaso", "riesgo"
104
+ ]
105
+ },
106
+ de: {
107
+ authority: [
108
+ "ultimativ", "vollständig", "leitfaden", "experte", "offiziell", "zertifiziert", "beste", "bester", "vergleich", "profi"
109
+ ],
110
+ urgency: [
111
+ "schnell", "jetzt", "sofort", "2026", "in 5 minuten", "heute", "dringend", "einfach"
112
+ ],
113
+ value: [
114
+ "kostenlos", "gratis", "sparen", "garantiert", "rabatt", "günstig", "angebot", "profitabel"
115
+ ],
116
+ curiosity: [
117
+ "geheimnis", "enthüllt", "tricks", "tipps", "warum", "wie man", "unglaublich", "entdecken"
118
+ ],
119
+ sentimentPositive: [
120
+ "hervorragend", "perfekt", "genial", "erfolg", "fantastisch", "wunderbar", "stark"
121
+ ],
122
+ sentimentNegative: [
123
+ "fehler", "gefahr", "vermeiden", "schlimmste", "betrug", "risiko", "warnung"
124
+ ]
125
+ },
126
+ it: {
127
+ authority: [
128
+ "guida", "completa", "esperto", "ufficiale", "certificato", "migliore", "migliori", "confronto", "definitiva", "professionale"
129
+ ],
130
+ urgency: [
131
+ "veloce", "ora", "subito", "immediato", "2026", "in 5 minuti", "oggi", "urgente"
132
+ ],
133
+ value: [
134
+ "gratis", "gratuito", "risparmia", "garantito", "sconto", "facile", "offerta", "conveniente"
135
+ ],
136
+ curiosity: [
137
+ "segreto", "svelato", "trucchi", "perché", "come fare", "incredibile", "scopri", "metodo"
138
+ ],
139
+ sentimentPositive: [
140
+ "perfetto", "incredibile", "fantastico", "successo", "eccezionale", "meraviglioso"
141
+ ],
142
+ sentimentNegative: [
143
+ "errore", "pericolo", "evitare", "peggiore", "truffa", "rischio", "trappola"
144
+ ]
145
+ },
146
+ nl: {
147
+ authority: ["ultiem", "gids", "expert", "officieel", "beste", "vergelijk", "compleet", "bewezen"],
148
+ urgency: ["snel", "nu", "direct", "vandaag", "2026", "meteen", "eenvoudig"],
149
+ value: ["gratis", "bespaar", "gegarandeerd", "korting", "goedkoop", "voordeel"],
150
+ curiosity: ["geheim", "onthuld", "tips", "waarom", "hoe", "ontdek"],
151
+ sentimentPositive: ["geweldig", "perfect", "uitstekend", "succes", "fantastisch"],
152
+ sentimentNegative: ["fout", "gevaar", "vermijd", "slechtste", "oplichting", "risico"]
153
+ },
154
+ pt: {
155
+ authority: ["guia", "completo", "definitivo", "especialista", "oficial", "melhor", "melhores", "comparativo"],
156
+ urgency: ["rápido", "agora", "imediato", "hoje", "2026", "em 5 minutos", "urgente"],
157
+ value: ["grátis", "gratuito", "economize", "garantido", "desconto", "fácil", "oferta"],
158
+ curiosity: ["segredo", "revelado", "dicas", "por que", "como fazer", "incrível", "descubra"],
159
+ sentimentPositive: ["perfeito", "incrível", "fantástico", "sucesso", "excelente"],
160
+ sentimentNegative: ["erro", "perigo", "evite", "pior", "golpe", "risco", "cilada"]
161
+ },
162
+ ar: {
163
+ authority: ["دليل", "شامل", "رسمي", "خبير", "أفضل", "مقارنة", "معتمد", "نهائي"],
164
+ urgency: ["سريع", "الآن", "فوري", "اليوم", "2026", "في دقائق", "عاجل"],
165
+ value: ["مجانا", "وفر", "مضمون", "خصم", "سهل", "عرض", "مميز"],
166
+ curiosity: ["سر", "كشف", "أسرار", "لماذا", "كيف", "مذهل", "اكتشف"],
167
+ sentimentPositive: ["رائع", "ممتاز", "ناجح", "مبهر", "مثالي"],
168
+ sentimentNegative: ["خطأ", "تحذير", "تجنب", "فخ", "احتيال", "خطر"]
169
+ },
170
+ ru: {
171
+ authority: ["руководство", "полный", "эксперт", "официальный", "лучший", "лучшие", "сравнение", "проверенный"],
172
+ urgency: ["быстро", "сейчас", "срочно", "сегодня", "2026", "за 5 минут", "немедленно"],
173
+ value: ["бесплатно", "скидка", "гарантия", "выгодно", "легко", "акция"],
174
+ curiosity: ["секрет", "раскрыт", "лайфхаки", "почему", "как", "невероятный", "узнайте"],
175
+ sentimentPositive: ["отлично", "идеально", "успех", "потрясающий", "великолепный"],
176
+ sentimentNegative: ["ошибка", "опасность", "избегать", "худший", "обман", "риск"]
177
+ },
178
+ zh: {
179
+ authority: ["终极", "完整", "指南", "专家", "官方", "最佳", "对比", "评测", "权威"],
180
+ urgency: ["快速", "立即", "今天", "2026", "5分钟", "马上", "紧急"],
181
+ value: ["免费", "省钱", "保证", "优惠", "简单", "折扣", "高性价比"],
182
+ curiosity: ["秘密", "揭秘", "技巧", "为什么", "如何", "必看", "探索"],
183
+ sentimentPositive: ["完美", "惊人", "卓越", "成功", "极佳", "出色"],
184
+ sentimentNegative: ["错误", "陷阱", "避免", "危险", "骗局", "风险"]
185
+ },
186
+ ja: {
187
+ authority: ["完全ガイド", "究極", "公式", "専門家", "おすすめ", "徹底比較", "決定版", "プロ"],
188
+ urgency: ["今すぐ", "速報", "最短", "2026年", "今日", "5分で", "簡単"],
189
+ value: ["無料", "お得", "割引", "保証", "コスパ", "特典", "プレゼント"],
190
+ curiosity: ["秘密", "裏ワザ", "暴露", "なぜ", "方法", "必見", "真実"],
191
+ sentimentPositive: ["最高", "完璧", "圧倒的", "成功", "絶賛", "素晴らしい"],
192
+ sentimentNegative: ["失敗", "注意", "危険", "避けるべき", "詐欺", "罠"]
193
+ },
194
+ hi: {
195
+ authority: ["गाइड", "सम्पूर्ण", "विशेषज्ञ", "आधिकारिक", "सर्वश्रेष्ठ", "तुलना", "सटीक"],
196
+ urgency: ["तुरंत", "अभी", "आज", "2026", "5 मिनट में", "जल्दी"],
197
+ value: ["मुफ्त", "बचत", "गारंटी", "छूट", "आसान", "ऑफर"],
198
+ curiosity: ["रहस्य", "खुलासा", "टिप्स", "क्यों", "कैसे", "अनोखा"],
199
+ sentimentPositive: ["उत्कृष्ट", "शानदार", "सफल", "अद्भुत", "बेहतरीन"],
200
+ sentimentNegative: ["गलती", "खतरा", "सावधान", "धोखा", "नुकसान"]
201
+ }
202
+ };
203
+
204
+ export class PowerWordsPsychologyEngine {
205
+ /**
206
+ * Register a custom language dictionary at runtime for infinite language extensibility
207
+ */
208
+ static registerLanguageWords(langCode: string, words: Partial<CategoryWordBank>): void {
209
+ const code = langCode.toLowerCase().trim();
210
+ if (!MULTILINGUAL_POWER_WORDS[code]) {
211
+ MULTILINGUAL_POWER_WORDS[code] = {
212
+ authority: [],
213
+ urgency: [],
214
+ value: [],
215
+ curiosity: [],
216
+ sentimentPositive: [],
217
+ sentimentNegative: [],
218
+ };
219
+ }
220
+ if (words.authority) MULTILINGUAL_POWER_WORDS[code].authority.push(...words.authority);
221
+ if (words.urgency) MULTILINGUAL_POWER_WORDS[code].urgency.push(...words.urgency);
222
+ if (words.value) MULTILINGUAL_POWER_WORDS[code].value.push(...words.value);
223
+ if (words.curiosity) MULTILINGUAL_POWER_WORDS[code].curiosity.push(...words.curiosity);
224
+ if (words.sentimentPositive) MULTILINGUAL_POWER_WORDS[code].sentimentPositive.push(...words.sentimentPositive);
225
+ if (words.sentimentNegative) MULTILINGUAL_POWER_WORDS[code].sentimentNegative.push(...words.sentimentNegative);
226
+ }
227
+
228
+ /**
229
+ * Get all registered language codes
230
+ */
231
+ static getSupportedLanguages(): string[] {
232
+ return Object.keys(MULTILINGUAL_POWER_WORDS);
233
+ }
234
+
235
+ /**
236
+ * Analyze title psychology and CTR potential in ANY language
237
+ */
238
+ static analyzeTitle(title: string, lang: LanguageCode = "fr"): PsychologyAnalysisResult {
239
+ const raw = (title || "").toLowerCase().trim();
240
+ const langKey = lang.toLowerCase().trim();
241
+
242
+ // Multi-bank search: search primary language, plus universal English / French fallback
243
+ const primaryBank = MULTILINGUAL_POWER_WORDS[langKey] || MULTILINGUAL_POWER_WORDS["en"] || MULTILINGUAL_POWER_WORDS["fr"];
244
+ const allBanks = [primaryBank];
245
+ if (langKey !== "en" && MULTILINGUAL_POWER_WORDS["en"]) allBanks.push(MULTILINGUAL_POWER_WORDS["en"]);
246
+
247
+ const detectedWords: string[] = [];
248
+ let authorityMatch = false;
249
+ let urgencyMatch = false;
250
+ let valueMatch = false;
251
+ let curiosityMatch = false;
252
+ let positiveCount = 0;
253
+ let negativeCount = 0;
254
+
255
+ for (const bank of allBanks) {
256
+ for (const word of bank.authority) {
257
+ if (raw.includes(word.toLowerCase())) {
258
+ authorityMatch = true;
259
+ if (!detectedWords.includes(word)) detectedWords.push(word);
260
+ }
261
+ }
262
+ for (const word of bank.urgency) {
263
+ if (raw.includes(word.toLowerCase())) {
264
+ urgencyMatch = true;
265
+ if (!detectedWords.includes(word)) detectedWords.push(word);
266
+ }
267
+ }
268
+ for (const word of bank.value) {
269
+ if (raw.includes(word.toLowerCase())) {
270
+ valueMatch = true;
271
+ if (!detectedWords.includes(word)) detectedWords.push(word);
272
+ }
273
+ }
274
+ for (const word of bank.curiosity) {
275
+ if (raw.includes(word.toLowerCase())) {
276
+ curiosityMatch = true;
277
+ if (!detectedWords.includes(word)) detectedWords.push(word);
278
+ }
279
+ }
280
+ for (const word of bank.sentimentPositive) {
281
+ if (raw.includes(word.toLowerCase())) {
282
+ positiveCount++;
283
+ if (!detectedWords.includes(word)) detectedWords.push(word);
284
+ }
285
+ }
286
+ for (const word of bank.sentimentNegative) {
287
+ if (raw.includes(word.toLowerCase())) {
288
+ negativeCount++;
289
+ if (!detectedWords.includes(word)) detectedWords.push(word);
290
+ }
291
+ }
292
+ }
293
+
294
+ // Universal Language-Agnostic Triggers
295
+ const hasNumber = /\b\d+\b/.test(raw);
296
+ const hasYear = /\b(202[4-9]|203[0-9])\b/.test(raw);
297
+ const hasCurrencyOrDiscount = /[\$€£¥₹%]|د\.إ|﷼|\b(promo|soldes|discount|deal|off)\b/i.test(raw);
298
+ const hasBracketsOrParentheses = /[\[\]\(\)\{\}【】《》「」]/.test(raw);
299
+
300
+ // Score Calculation (0 - 100)
301
+ let score = 30; // base score
302
+ if (detectedWords.length >= 1) score += 20;
303
+ if (detectedWords.length >= 2) score += 10;
304
+ if (detectedWords.length >= 3) score += 10;
305
+ if (hasNumber) score += 15;
306
+ if (hasYear) score += 10;
307
+ if (hasCurrencyOrDiscount) score += 5;
308
+ if (hasBracketsOrParentheses) score += 5;
309
+ if (score > 100) score = 100;
310
+
311
+ let sentiment: "positif" | "negatif" | "neutre" = "neutre";
312
+ if (positiveCount > negativeCount) sentiment = "positif";
313
+ else if (negativeCount > positiveCount) sentiment = "negatif";
314
+
315
+ const recommendations: string[] = [];
316
+ if (detectedWords.length === 0) {
317
+ recommendations.push("Ajoutez au moins 1 mot d'impact émotionnel ou d'autorité (ex: 'Guide', 'Ultime', 'Meilleur').");
318
+ }
319
+ if (!hasNumber) {
320
+ recommendations.push("Intégrez un chiffre (ex: 'Les 7 Meilleurs...') pour augmenter le taux de clic de +36%.");
321
+ }
322
+ if (!hasYear) {
323
+ recommendations.push("Indiquez l'année courante (2026) pour signaler la fraîcheur du contenu aux internautes.");
324
+ }
325
+ if (!hasBracketsOrParentheses) {
326
+ recommendations.push("Utilisez des crochets ou parenthèses [Guide 2026] pour structurer visuellement l'extrait.");
327
+ }
328
+
329
+ // Suggested high CTR titles
330
+ const cleanSubject = title.replace(/\(.*?\)|\[.*?\]/g, "").trim();
331
+ const suggestedTitles = [
332
+ `Les 7 Meilleurs ${cleanSubject} [Guide Ultime 2026]`,
333
+ `Comment Maîtriser ${cleanSubject} en 5 Étapes (Méthode 2026)`,
334
+ `${cleanSubject} : Avis, Comparatif et Pièges à Éviter (2026)`
335
+ ];
336
+
337
+ return {
338
+ score,
339
+ powerWordsDetected: detectedWords,
340
+ sentiment,
341
+ hasNumber,
342
+ hasYear,
343
+ hasCuriosityTrigger: curiosityMatch,
344
+ hasUrgencyTrigger: urgencyMatch,
345
+ hasAuthorityTrigger: authorityMatch,
346
+ hasCurrencyOrDiscount,
347
+ hasBracketsOrParentheses,
348
+ recommendations,
349
+ suggestedTitles
350
+ };
351
+ }
352
+
353
+ /**
354
+ * Automatically enriches a programmatic title in ANY language with optimal psychology triggers
355
+ */
356
+ static enrichProgrammaticTitle(baseTitle: string, lang: LanguageCode = "fr"): string {
357
+ const analysis = this.analyzeTitle(baseTitle, lang);
358
+ if (analysis.score >= 80) return baseTitle;
359
+
360
+ let enriched = baseTitle.trim();
361
+ if (!analysis.hasNumber && !enriched.startsWith("Top ") && !enriched.startsWith("Les ")) {
362
+ enriched = `Les Meilleurs ${enriched}`;
363
+ }
364
+ if (!analysis.hasYear) {
365
+ enriched = `${enriched} (Guide 2026)`;
366
+ }
367
+ return enriched;
368
+ }
369
+ }