@lynxflow/seo-engine 1.8.2 → 1.8.4

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.
@@ -1,9 +1,19 @@
1
1
  /**
2
- * 🧠 Multilingual Power Words & Click Psychology Engine
3
- * High-Converting CTR Optimizers & Sentiment Analysis across FR, EN, ES, DE, IT
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 = "fr" | "en" | "es" | "de" | "it" | "nl" | "pt";
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
- export const MULTILINGUAL_POWER_WORDS: Record<LanguageCode, {
22
- authority: string[];
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
- "overnight", "rapid", "minutes"
67
+ "in 5 minutes", "immediate", "urgent", "unlock", "rapid"
62
68
  ],
63
69
  value: [
64
- "free", "affordable", "guaranteed", "save", "profit", "profitable", "bonus", "cheap", "priceless",
65
- "effortless", "lifetime", "budget"
70
+ "free", "affordable", "save", "guaranteed", "easy", "bonus", "discount", "effortless",
71
+ "lifetime", "roi", "value", "exclusive deal", "profitable"
66
72
  ],
67
73
  curiosity: [
68
- "secret", "secrets", "exclusive", "revealed", "hacks", "tricks", "hacks", "behind the scenes",
69
- "uncovered", "surprising", "insider", "confidential", "jaw-dropping"
74
+ "secret", "revealed", "insider", "hacks", "why", "how to", "untold", "surprising",
75
+ "shocking", "hidden", "must-know", "unbelievable", "myth"
70
76
  ],
71
77
  sentimentPositive: [
72
- "amazing", "awesome", "brilliant", "epic", "legendary", "superb", "stunning", "perfect", "powerful",
73
- "triumph", "miracle", "flawless"
78
+ "amazing", "brilliant", "powerful", "epic", "perfect", "stunning", "thriving", "seamless",
79
+ "exceptional", "masterpiece", "unbeatable"
74
80
  ],
75
81
  sentimentNegative: [
76
- "mistake", "warning", "deadly", "worst", "danger", "toxic", "destroy", "ruin", "scam", "nightmare"
82
+ "mistake", "warning", "fail", "avoid", "risk", "trap", "worst", "scam", "danger", "flaw"
77
83
  ]
78
84
  },
79
85
  es: {
80
- authority: ["guía", "completo", "definitivo", "experto", "oficial", "mejor", "mejores", "certificado", "profesional", "maestro"],
81
- urgency: ["rápido", "ahora", "inmediato", "2026", "hoy", "urgente", "en minutos", "paso a paso"],
82
- value: ["gratis", "gratuito", "económico", "garantizado", "ahorra", "rentable", "oferta", "sin riesgo"],
83
- curiosity: ["secreto", "exclusivo", "revelado", "trucos", "increíble", "top", "método", "descubre"],
84
- sentimentPositive: ["increíble", "fantástico", "perfecto", "excelente", "maravilloso", "potente", "éxito"],
85
- sentimentNegative: ["error", "peligro", "evitar", "peor", "estafa", "trampa", "arruinar"]
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: ["leitfaden", "komplett", "experte", "offiziell", "beste", "bester", "zertifiziert", "vergleich", "meister"],
89
- urgency: ["schnell", "jetzt", "sofort", "2026", "heute", "schritt-für-schritt", "in 5 minuten"],
90
- value: ["kostenlos", "gratis", "sparen", "garantiert", "günstig", "rendite", "ohne risiko"],
91
- curiosity: ["geheim", "exklusiv", "tipps", "tricks", "enthüllt", "top", "warum", "entdecken"],
92
- sentimentPositive: ["perfekt", "genial", "hervorragend", "leistungsstark", "erfolg", "wunderbar"],
93
- sentimentNegative: ["fehler", "gefahr", "vermeiden", "schlimmste", "falle", "risiko"]
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: ["guida", "completa", "esperto", "ufficiale", "migliore", "migliori", "certificato", "professionale"],
97
- urgency: ["veloce", "ora", "subito", "2026", "oggi", "passo dopo passo", "in pochi minuti"],
98
- value: ["gratis", "gratuito", "economico", "garantito", "risparmio", "conveniente", "senza rischi"],
99
- curiosity: ["segreto", "esclusivo", "trucchi", "rivelato", "top", "metodo", "scopri"],
100
- sentimentPositive: ["perfetto", "fantastico", "eccellente", "potente", "successo", "incredibile"],
101
- sentimentNegative: ["errore", "pericolo", "evitare", "peggiore", "truffa", "trappola"]
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: ["gids", "compleet", "expert", "officieel", "beste", "gecertificeerd", "professioneel"],
105
- urgency: ["snel", "nu", "direct", "2026", "vandaag", "stap voor stap"],
106
- value: ["gratis", "goedkoop", "gegarandeerd", "bespaar", "rendement", "zonder risico"],
107
- curiosity: ["geheim", "exclusief", "tips", "onthuld", "top", "ontdek"],
108
- sentimentPositive: ["perfect", "geweldig", "uitstekend", "krachtig", "succes"],
109
- sentimentNegative: ["fout", "gevaar", "vermijden", "slechtste", "valkuil"]
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", "certificado"],
113
- urgency: ["rápido", "agora", "imediato", "2026", "hoje", "passo a passo"],
114
- value: ["grátis", "gratuito", "econômico", "garantido", "economize", "sem risco"],
115
- curiosity: ["segredo", "exclusivo", "revelado", "truques", "top", "descubra"],
116
- sentimentPositive: ["incrível", "perfeito", "excelente", "poderoso", "sucesso"],
117
- sentimentNegative: ["erro", "perigo", "evitar", "pior", "golpe"]
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
- * Analyzes headline/title psychology, CTR triggers, sentiment, and Google SERP potential
206
+ * Register a custom language dictionary at runtime for infinite language extensibility
124
207
  */
125
- static analyzeTitlePsychology(title: string, lang: LanguageCode = "fr"): PsychologyAnalysisResult {
126
- const cleanTitle = title.toLowerCase();
127
- const dict = MULTILINGUAL_POWER_WORDS[lang] || MULTILINGUAL_POWER_WORDS.fr;
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
- const detectedPowerWords: string[] = [];
130
- let score = 30; // base score
228
+ /**
229
+ * Get all registered language codes
230
+ */
231
+ static getSupportedLanguages(): string[] {
232
+ return Object.keys(MULTILINGUAL_POWER_WORDS);
233
+ }
131
234
 
132
- // 1. Check Authority Triggers
133
- const hasAuthority = dict.authority.some(w => {
134
- if (cleanTitle.includes(w)) {
135
- detectedPowerWords.push(w);
136
- return true;
137
- }
138
- return false;
139
- });
140
- if (hasAuthority) score += 20;
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
- // 2. Check Urgency & Timeliness Triggers
143
- const hasUrgency = dict.urgency.some(w => {
144
- if (cleanTitle.includes(w)) {
145
- detectedPowerWords.push(w);
146
- return true;
147
- }
148
- return false;
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
- // 3. Check Value Triggers
153
- const hasValue = dict.value.some(w => {
154
- if (cleanTitle.includes(w)) {
155
- detectedPowerWords.push(w);
156
- return true;
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
- return false;
159
- });
160
- if (hasValue) score += 15;
161
-
162
- // 4. Check Curiosity & Exclusivity Triggers
163
- const hasCuriosity = dict.curiosity.some(w => {
164
- if (cleanTitle.includes(w)) {
165
- detectedPowerWords.push(w);
166
- return true;
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
- return false;
169
- });
170
- if (hasCuriosity) score += 10;
292
+ }
171
293
 
172
- // 5. Check Number & Current Year (BuzzSumo & Conductor: +36% CTR)
173
- const hasNumber = /\d+/.test(title);
174
- if (hasNumber) score += 10;
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
- const currentYear = new Date().getFullYear().toString();
177
- const hasYear = title.includes(currentYear) || title.includes("2026");
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
- const hasPositive = dict.sentimentPositive.some(w => cleanTitle.includes(w));
182
- const hasNegative = dict.sentimentNegative.some(w => cleanTitle.includes(w));
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 (!detectedPowerWords.length) {
192
- recommendations.push("Ajoutez au moins un mot d'impact (*Power Word*) comme 'Guide Complet', 'Meilleur' ou 'Ultime'.");
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 ou l'année en cours (ex: '7 astuces', '2026') pour booster le taux de clic de +36%.");
320
+ recommendations.push("Intégrez un chiffre (ex: 'Les 7 Meilleurs...') pour augmenter le taux de clic de +36%.");
196
321
  }
197
- if (title.length < 35) {
198
- recommendations.push("Titre trop court : enrichissez votre promesse de valeur pour occuper les 600px de la SERP Google.");
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
- // 8. Dynamic High-CTR Suggestions Generator
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: Math.min(100, score),
215
- powerWordsDetected: Array.from(new Set(detectedPowerWords)),
338
+ score,
339
+ powerWordsDetected: detectedWords,
216
340
  sentiment,
217
341
  hasNumber,
218
342
  hasYear,
219
- hasCuriosityTrigger: hasCuriosity,
220
- hasUrgencyTrigger: hasUrgency,
221
- hasAuthorityTrigger: hasAuthority,
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 high-converting click psychology power words
354
+ * Automatically enriches a programmatic title in ANY language with optimal psychology triggers
229
355
  */
230
- static enrichProgrammaticTitle(service: string, locationOrCompetitor: string, type: "geo" | "vs" | "alt" = "geo", lang: LanguageCode = "fr"): string {
231
- const year = "2026";
232
- if (lang === "fr") {
233
- if (type === "geo") {
234
- return `Meilleur ${service} à ${locationOrCompetitor} : Guide Complet ${year}`;
235
- } else if (type === "vs") {
236
- return `${service} vs ${locationOrCompetitor} : Le Comparatif Ultime & Avis`;
237
- } else {
238
- return `Top 5 Alternatives à ${locationOrCompetitor} : Comparatif ${year}`;
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
  }