@lynxflow/seo-engine 1.8.2 → 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.
- package/connectors/wordpress/lynxseo-admin-app.js +25 -0
- package/connectors/wordpress/lynxseo-connector.php +24 -0
- package/connectors/wordpress/lynxseo-connector.zip +0 -0
- package/dist/extended-schemas.d.ts +2 -2
- package/dist/index.js +559 -339
- package/dist/index.mjs +10 -7
- package/package.json +1 -1
- package/src/extended-schemas.ts +8 -4
- package/src/og-image-generator.ts +5 -5
- package/src/power-words-psychology.ts +269 -160
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 🧠 Multilingual Power Words & Click Psychology Engine
|
|
3
|
-
*
|
|
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.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
export type LanguageCode =
|
|
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
|
+
}
|
|
7
17
|
|
|
8
18
|
export interface PsychologyAnalysisResult {
|
|
9
19
|
score: number; // 0 to 100
|
|
@@ -14,247 +24,346 @@ export interface PsychologyAnalysisResult {
|
|
|
14
24
|
hasCuriosityTrigger: boolean;
|
|
15
25
|
hasUrgencyTrigger: boolean;
|
|
16
26
|
hasAuthorityTrigger: boolean;
|
|
27
|
+
hasCurrencyOrDiscount: boolean;
|
|
28
|
+
hasBracketsOrParentheses: boolean;
|
|
17
29
|
recommendations: string[];
|
|
18
30
|
suggestedTitles: string[];
|
|
19
31
|
}
|
|
20
32
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
urgency: string[];
|
|
24
|
-
value: string[];
|
|
25
|
-
curiosity: string[];
|
|
26
|
-
sentimentPositive: string[];
|
|
27
|
-
sentimentNegative: string[];
|
|
28
|
-
}> = {
|
|
33
|
+
// Pre-loaded high-converting dictionaries across global languages
|
|
34
|
+
export const MULTILINGUAL_POWER_WORDS: Record<string, CategoryWordBank> = {
|
|
29
35
|
fr: {
|
|
30
36
|
authority: [
|
|
31
37
|
"officiel", "guide", "expert", "complet", "certifié", "ultime", "meilleur", "meilleurs", "comparatif",
|
|
32
|
-
"maîtriser", "spécialiste", "leader", "professionnel", "authentique", "infaillible", "reconnu", "démontré"
|
|
38
|
+
"maîtriser", "spécialiste", "leader", "professionnel", "authentique", "infaillible", "reconnu", "démontré", "avis"
|
|
33
39
|
],
|
|
34
40
|
urgency: [
|
|
35
41
|
"rapide", "instantané", "maintenant", "immédiat", "express", "2026", "dernière minute", "urgent",
|
|
36
|
-
"dépêchez-vous", "en 5 min", "en 10 min", "aujourd'hui", "débloquer"
|
|
42
|
+
"dépêchez-vous", "en 5 min", "en 10 min", "aujourd'hui", "débloquer", "vite"
|
|
37
43
|
],
|
|
38
44
|
value: [
|
|
39
45
|
"gratuit", "économique", "rentable", "offert", "sans engagement", "garanti", "facile", "clé en main",
|
|
40
|
-
"rentabilité", "gain", "économisez", "inclus", "accessible", "inédit"
|
|
46
|
+
"rentabilité", "gain", "économisez", "inclus", "accessible", "inédit", "remise", "promo"
|
|
41
47
|
],
|
|
42
48
|
curiosity: [
|
|
43
49
|
"exclusif", "secret", "astuces", "révélé", "inédit", "top", "méthode", "pourquoi", "comment",
|
|
44
|
-
"découvrir", "choc", "incontournable", "mystère", "incroyable"
|
|
50
|
+
"découvrir", "choc", "incontournable", "mystère", "incroyable", "vérité"
|
|
45
51
|
],
|
|
46
52
|
sentimentPositive: [
|
|
47
53
|
"parfait", "incroyable", "magnifique", "puissant", "réussi", "génial", "exceptionnel", "facile",
|
|
48
54
|
"meilleur", "succès", "brillant", "splendide", "radieux", "magique", "sublime"
|
|
49
55
|
],
|
|
50
56
|
sentimentNegative: [
|
|
51
|
-
"erreur", "piège", "danger", "éviter", "pire", "arnaque", "catastrophe", "risqué", "menace", "perte"
|
|
57
|
+
"erreur", "piège", "danger", "éviter", "pire", "arnaque", "catastrophe", "risqué", "menace", "perte", "alerte"
|
|
52
58
|
]
|
|
53
59
|
},
|
|
54
60
|
en: {
|
|
55
61
|
authority: [
|
|
56
62
|
"ultimate", "complete", "definitive", "guide", "expert", "official", "proven", "master", "certified",
|
|
57
|
-
"best", "top", "authority", "case study", "benchmark", "professional"
|
|
63
|
+
"best", "top", "authority", "case study", "benchmark", "professional", "review", "formula"
|
|
58
64
|
],
|
|
59
65
|
urgency: [
|
|
60
66
|
"instant", "fast", "quick", "now", "today", "2026", "deadline", "last chance", "step-by-step",
|
|
61
|
-
"
|
|
67
|
+
"in 5 minutes", "immediate", "urgent", "unlock", "rapid"
|
|
62
68
|
],
|
|
63
69
|
value: [
|
|
64
|
-
"free", "affordable", "
|
|
65
|
-
"
|
|
70
|
+
"free", "affordable", "save", "guaranteed", "easy", "bonus", "discount", "effortless",
|
|
71
|
+
"lifetime", "roi", "value", "exclusive deal", "profitable"
|
|
66
72
|
],
|
|
67
73
|
curiosity: [
|
|
68
|
-
"secret", "
|
|
69
|
-
"
|
|
74
|
+
"secret", "revealed", "insider", "hacks", "why", "how to", "untold", "surprising",
|
|
75
|
+
"shocking", "hidden", "must-know", "unbelievable", "myth"
|
|
70
76
|
],
|
|
71
77
|
sentimentPositive: [
|
|
72
|
-
"amazing", "
|
|
73
|
-
"
|
|
78
|
+
"amazing", "brilliant", "powerful", "epic", "perfect", "stunning", "thriving", "seamless",
|
|
79
|
+
"exceptional", "masterpiece", "unbeatable"
|
|
74
80
|
],
|
|
75
81
|
sentimentNegative: [
|
|
76
|
-
"mistake", "warning", "
|
|
82
|
+
"mistake", "warning", "fail", "avoid", "risk", "trap", "worst", "scam", "danger", "flaw"
|
|
77
83
|
]
|
|
78
84
|
},
|
|
79
85
|
es: {
|
|
80
|
-
authority: [
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
]
|
|
86
105
|
},
|
|
87
106
|
de: {
|
|
88
|
-
authority: [
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
]
|
|
94
125
|
},
|
|
95
126
|
it: {
|
|
96
|
-
authority: [
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
]
|
|
102
145
|
},
|
|
103
146
|
nl: {
|
|
104
|
-
authority: ["
|
|
105
|
-
urgency: ["snel", "nu", "direct", "
|
|
106
|
-
value: ["gratis", "
|
|
107
|
-
curiosity: ["geheim", "
|
|
108
|
-
sentimentPositive: ["
|
|
109
|
-
sentimentNegative: ["fout", "gevaar", "
|
|
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"]
|
|
110
153
|
},
|
|
111
154
|
pt: {
|
|
112
|
-
authority: ["guia", "completo", "definitivo", "especialista", "oficial", "melhor", "melhores", "
|
|
113
|
-
urgency: ["rápido", "agora", "imediato", "
|
|
114
|
-
value: ["grátis", "gratuito", "
|
|
115
|
-
curiosity: ["segredo", "
|
|
116
|
-
sentimentPositive: ["
|
|
117
|
-
sentimentNegative: ["erro", "perigo", "
|
|
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: ["गलती", "खतरा", "सावधान", "धोखा", "नुकसान"]
|
|
118
201
|
}
|
|
119
202
|
};
|
|
120
203
|
|
|
121
204
|
export class PowerWordsPsychologyEngine {
|
|
122
205
|
/**
|
|
123
|
-
*
|
|
206
|
+
* Register a custom language dictionary at runtime for infinite language extensibility
|
|
124
207
|
*/
|
|
125
|
-
static
|
|
126
|
-
const
|
|
127
|
-
|
|
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
|
+
}
|
|
128
227
|
|
|
129
|
-
|
|
130
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Get all registered language codes
|
|
230
|
+
*/
|
|
231
|
+
static getSupportedLanguages(): string[] {
|
|
232
|
+
return Object.keys(MULTILINGUAL_POWER_WORDS);
|
|
233
|
+
}
|
|
131
234
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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"]);
|
|
141
246
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
});
|
|
150
|
-
if (hasUrgency) score += 15;
|
|
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;
|
|
151
254
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
}
|
|
157
261
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
+
}
|
|
167
291
|
}
|
|
168
|
-
|
|
169
|
-
});
|
|
170
|
-
if (hasCuriosity) score += 10;
|
|
292
|
+
}
|
|
171
293
|
|
|
172
|
-
//
|
|
173
|
-
const hasNumber = /\d
|
|
174
|
-
|
|
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);
|
|
175
299
|
|
|
176
|
-
|
|
177
|
-
|
|
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;
|
|
178
310
|
|
|
179
|
-
// 6. Sentiment Detection
|
|
180
311
|
let sentiment: "positif" | "negatif" | "neutre" = "neutre";
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (hasPositive) {
|
|
184
|
-
sentiment = "positif";
|
|
185
|
-
} else if (hasNegative) {
|
|
186
|
-
sentiment = "negatif";
|
|
187
|
-
}
|
|
312
|
+
if (positiveCount > negativeCount) sentiment = "positif";
|
|
313
|
+
else if (negativeCount > positiveCount) sentiment = "negatif";
|
|
188
314
|
|
|
189
|
-
// 7. Recommendations
|
|
190
315
|
const recommendations: string[] = [];
|
|
191
|
-
if (
|
|
192
|
-
recommendations.push("Ajoutez au moins
|
|
316
|
+
if (detectedWords.length === 0) {
|
|
317
|
+
recommendations.push("Ajoutez au moins 1 mot d'impact émotionnel ou d'autorité (ex: 'Guide', 'Ultime', 'Meilleur').");
|
|
193
318
|
}
|
|
194
319
|
if (!hasNumber) {
|
|
195
|
-
recommendations.push("Intégrez un chiffre
|
|
320
|
+
recommendations.push("Intégrez un chiffre (ex: 'Les 7 Meilleurs...') pour augmenter le taux de clic de +36%.");
|
|
196
321
|
}
|
|
197
|
-
if (
|
|
198
|
-
recommendations.push("
|
|
322
|
+
if (!hasYear) {
|
|
323
|
+
recommendations.push("Indiquez l'année courante (2026) pour signaler la fraîcheur du contenu aux internautes.");
|
|
199
324
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const suggestedTitles: string[] = [];
|
|
203
|
-
if (lang === "fr") {
|
|
204
|
-
suggestedTitles.push(`${title} : Guide Ultime 2026`);
|
|
205
|
-
suggestedTitles.push(`Les 5 Meilleurs ${title} (Comparatif & Avis)`);
|
|
206
|
-
suggestedTitles.push(`${title} — Solution Rapide & Complète`);
|
|
207
|
-
} else {
|
|
208
|
-
suggestedTitles.push(`${title}: The Ultimate 2026 Guide`);
|
|
209
|
-
suggestedTitles.push(`Top 5 Best ${title} (Full Review & Comparison)`);
|
|
210
|
-
suggestedTitles.push(`${title} — Proven & Fast Method`);
|
|
325
|
+
if (!hasBracketsOrParentheses) {
|
|
326
|
+
recommendations.push("Utilisez des crochets ou parenthèses [Guide 2026] pour structurer visuellement l'extrait.");
|
|
211
327
|
}
|
|
212
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
|
+
|
|
213
337
|
return {
|
|
214
|
-
score
|
|
215
|
-
powerWordsDetected:
|
|
338
|
+
score,
|
|
339
|
+
powerWordsDetected: detectedWords,
|
|
216
340
|
sentiment,
|
|
217
341
|
hasNumber,
|
|
218
342
|
hasYear,
|
|
219
|
-
hasCuriosityTrigger:
|
|
220
|
-
hasUrgencyTrigger:
|
|
221
|
-
hasAuthorityTrigger:
|
|
343
|
+
hasCuriosityTrigger: curiosityMatch,
|
|
344
|
+
hasUrgencyTrigger: urgencyMatch,
|
|
345
|
+
hasAuthorityTrigger: authorityMatch,
|
|
346
|
+
hasCurrencyOrDiscount,
|
|
347
|
+
hasBracketsOrParentheses,
|
|
222
348
|
recommendations,
|
|
223
349
|
suggestedTitles
|
|
224
350
|
};
|
|
225
351
|
}
|
|
226
352
|
|
|
227
353
|
/**
|
|
228
|
-
* Automatically enriches a programmatic title with
|
|
354
|
+
* Automatically enriches a programmatic title in ANY language with optimal psychology triggers
|
|
229
355
|
*/
|
|
230
|
-
static enrichProgrammaticTitle(
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
} else if (lang === "en") {
|
|
241
|
-
if (type === "geo") {
|
|
242
|
-
return `Best ${service} in ${locationOrCompetitor}: Ultimate ${year} Guide`;
|
|
243
|
-
} else if (type === "vs") {
|
|
244
|
-
return `${service} vs ${locationOrCompetitor}: Definitive Comparison`;
|
|
245
|
-
} else {
|
|
246
|
-
return `Top 5 Alternatives to ${locationOrCompetitor} (${year} Review)`;
|
|
247
|
-
}
|
|
248
|
-
} else if (lang === "es") {
|
|
249
|
-
if (type === "geo") {
|
|
250
|
-
return `Mejor ${service} en ${locationOrCompetitor}: Guía Completa ${year}`;
|
|
251
|
-
} else {
|
|
252
|
-
return `${service} vs ${locationOrCompetitor}: Comparativa Definitiva`;
|
|
253
|
-
}
|
|
254
|
-
} else if (lang === "de") {
|
|
255
|
-
return `Beste ${service} in ${locationOrCompetitor}: Kompletter Leitfaden ${year}`;
|
|
256
|
-
} else {
|
|
257
|
-
return `${service} in ${locationOrCompetitor} (${year})`;
|
|
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)`;
|
|
258
366
|
}
|
|
367
|
+
return enriched;
|
|
259
368
|
}
|
|
260
369
|
}
|