@lynxflow/seo-engine 1.8.30 → 2.0.1

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.
Files changed (96) hide show
  1. package/README.md +60 -117
  2. package/dist/ai-copilot-client.d.ts +2 -0
  3. package/dist/built-in-locations.d.ts +22 -0
  4. package/dist/content-ai-arsenal.d.ts +28 -0
  5. package/dist/existing-media-harvester.d.ts +33 -0
  6. package/dist/extended-schemas.d.ts +84 -0
  7. package/dist/features-knowledge-harvester.d.ts +56 -0
  8. package/dist/geo-citation-scorer.d.ts +34 -0
  9. package/dist/i18n-detector.d.ts +1 -1
  10. package/dist/index.d.ts +32 -7
  11. package/dist/index.js +567 -83
  12. package/dist/index.mjs +2062 -169
  13. package/dist/internal-pagerank-graph.d.ts +59 -0
  14. package/dist/knowledge-bank-builder.d.ts +91 -0
  15. package/dist/marketing-skills-engine.d.ts +74 -0
  16. package/dist/matrix-engine.d.ts +29 -0
  17. package/dist/power-words-psychology.d.ts +27 -15
  18. package/dist/public-manifest-engine.d.ts +58 -0
  19. package/dist/rag-knowledge-engine.d.ts +50 -0
  20. package/dist/rate-limited-translator.d.ts +19 -0
  21. package/dist/token-quota-manager.d.ts +4 -0
  22. package/package.json +11 -3
  23. package/TUTORIEL_INTEGRATION_SITE.md +0 -283
  24. package/connectors/cloudflare-worker/worker.js +0 -106
  25. package/connectors/laravel/LynxSeoController.php +0 -37
  26. package/connectors/wordpress/lynxseo-admin-app.js +0 -2611
  27. package/connectors/wordpress/lynxseo-connector.php +0 -1712
  28. package/connectors/wordpress/lynxseo-connector.zip +0 -0
  29. package/lynxflow-seo-engine-1.2.0.tgz +0 -0
  30. package/src/ad-intelligence-cro.ts +0 -140
  31. package/src/ai-bots-log-analyzer.ts +0 -127
  32. package/src/ai-copilot-client.ts +0 -99
  33. package/src/analytics-client.ts +0 -315
  34. package/src/auth-key.ts +0 -262
  35. package/src/backlinks-client.ts +0 -85
  36. package/src/brand-dna-calendar.ts +0 -120
  37. package/src/brand-icons.ts +0 -138
  38. package/src/built-in-locations.ts +0 -170
  39. package/src/content-ai-arsenal.ts +0 -268
  40. package/src/copy-frameworks-master.ts +0 -89
  41. package/src/cro-copywriting-engine.ts +0 -105
  42. package/src/crosslink-scorer.ts +0 -101
  43. package/src/embeddable-seo-widget.ts +0 -57
  44. package/src/engine.test.ts +0 -386
  45. package/src/engine.ts +0 -813
  46. package/src/existing-media-harvester.ts +0 -98
  47. package/src/extended-schemas.ts +0 -589
  48. package/src/features-knowledge-harvester.ts +0 -190
  49. package/src/free-public-tools.ts +0 -224
  50. package/src/geo-mesh-linking.ts +0 -93
  51. package/src/google-business-profile.ts +0 -280
  52. package/src/i18n-detector.ts +0 -98
  53. package/src/i18n-dictionary.ts +0 -422
  54. package/src/index.ts +0 -164
  55. package/src/indexnow-client.ts +0 -94
  56. package/src/instant-matrix-search.ts +0 -48
  57. package/src/isr-cache-manager.ts +0 -78
  58. package/src/keyword-permutator.ts +0 -118
  59. package/src/knowledge-bank-builder.ts +0 -299
  60. package/src/knowledge-graph-linker.ts +0 -81
  61. package/src/lago-token-meter.ts +0 -120
  62. package/src/legal-disclaimers.ts +0 -407
  63. package/src/llm-content-cleaner.ts +0 -64
  64. package/src/llm-prompt.ts +0 -323
  65. package/src/master-marketing-engine.ts +0 -314
  66. package/src/matrix-engine.ts +0 -1204
  67. package/src/mcp-seo-server.ts +0 -108
  68. package/src/ngram-density-analyzer.ts +0 -108
  69. package/src/og-image-generator.ts +0 -109
  70. package/src/power-words-psychology.ts +0 -369
  71. package/src/public-manifest-engine.ts +0 -109
  72. package/src/rag-knowledge-engine.ts +0 -163
  73. package/src/rank-math-parity.ts +0 -235
  74. package/src/rate-limited-translator.ts +0 -110
  75. package/src/real-reviews-sync.ts +0 -207
  76. package/src/rss-syndication-feed.ts +0 -56
  77. package/src/schema-builder.ts +0 -134
  78. package/src/seo-opportunities-decay.ts +0 -154
  79. package/src/serp-client.ts +0 -89
  80. package/src/serp-history-alerts.ts +0 -75
  81. package/src/site-auditor.ts +0 -83
  82. package/src/site-crawler.ts +0 -406
  83. package/src/slug-engine.ts +0 -183
  84. package/src/social-ads-seo.ts +0 -211
  85. package/src/social-growth-suite.ts +0 -310
  86. package/src/social-trend-seo.ts +0 -103
  87. package/src/social-video-seo.ts +0 -100
  88. package/src/team-rbac.ts +0 -61
  89. package/src/technical-rules-auditor.ts +0 -171
  90. package/src/token-quota-manager.ts +0 -91
  91. package/src/types.ts +0 -143
  92. package/src/ui-icons.ts +0 -134
  93. package/src/urlytics-engine.ts +0 -97
  94. package/src/video-youtube-analyzer.ts +0 -81
  95. package/src/yoast-parity.ts +0 -161
  96. package/tsconfig.json +0 -15
package/src/ui-icons.ts DELETED
@@ -1,134 +0,0 @@
1
- /**
2
- * 🎨 Universal Zero-Dependency UI & Feature SVG Icons
3
- * Provides crisp, modern Lucide-style vector icons for features, trust badges, and UI components.
4
- * Includes automatic semantic keyword-to-icon detection for feature descriptions.
5
- */
6
-
7
- export interface UiIcon {
8
- name: string;
9
- viewBox: string;
10
- svgPath: string;
11
- }
12
-
13
- export const UI_ICONS: Record<string, UiIcon> = {
14
- zap: {
15
- name: "Zap / Lightning",
16
- viewBox: "0 0 24 24",
17
- svgPath: '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
18
- },
19
- shield: {
20
- name: "Shield / Security",
21
- viewBox: "0 0 24 24",
22
- svgPath: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
23
- },
24
- star: {
25
- name: "Star / Rating",
26
- viewBox: "0 0 24 24",
27
- svgPath: '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" fill="currentColor"/>',
28
- },
29
- chart: {
30
- name: "Chart / Analytics",
31
- viewBox: "0 0 24 24",
32
- svgPath: '<line x1="18" y1="20" x2="18" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="12" y1="20" x2="12" y2="4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="6" y1="20" x2="6" y2="14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
33
- },
34
- calendar: {
35
- name: "Calendar / Scheduling",
36
- viewBox: "0 0 24 24",
37
- svgPath: '<rect x="3" y="4" width="18" height="18" rx="2" ry="2" fill="none" stroke="currentColor" stroke-width="2"/><line x1="16" y1="2" x2="16" y2="6" stroke="currentColor" stroke-width="2"/><line x1="8" y1="2" x2="8" y2="6" stroke="currentColor" stroke-width="2"/><line x1="3" y1="10" x2="21" y2="10" stroke="currentColor" stroke-width="2"/>',
38
- },
39
- users: {
40
- name: "Users / Team",
41
- viewBox: "0 0 24 24",
42
- svgPath: '<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="9" cy="7" r="4" fill="none" stroke="currentColor" stroke-width="2"/><path d="M23 21v-2a4 4 0 0 0-3-3.87" fill="none" stroke="currentColor" stroke-width="2"/><path d="M16 3.13a4 4 0 0 1 0 7.75" fill="none" stroke="currentColor" stroke-width="2"/>',
43
- },
44
- rocket: {
45
- name: "Rocket / Speed",
46
- viewBox: "0 0 24 24",
47
- svgPath: '<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z" fill="none" stroke="currentColor" stroke-width="2"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" fill="none" stroke="currentColor" stroke-width="2"/>',
48
- },
49
- sparkles: {
50
- name: "Sparkles / AI",
51
- viewBox: "0 0 24 24",
52
- svgPath: '<path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3L12 3z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>',
53
- },
54
- check: {
55
- name: "Check / Success",
56
- viewBox: "0 0 24 24",
57
- svgPath: '<polyline points="20 6 9 17 4 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
58
- },
59
- lock: {
60
- name: "Lock / Privacy",
61
- viewBox: "0 0 24 24",
62
- svgPath: '<rect x="3" y="11" width="18" height="11" rx="2" ry="2" fill="none" stroke="currentColor" stroke-width="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4" fill="none" stroke="currentColor" stroke-width="2"/>',
63
- },
64
- clock: {
65
- name: "Clock / 24-7",
66
- viewBox: "0 0 24 24",
67
- svgPath: '<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2"/><polyline points="12 6 12 12 16 14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
68
- },
69
- phone: {
70
- name: "Phone / Contact",
71
- viewBox: "0 0 24 24",
72
- svgPath: '<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" fill="none" stroke="currentColor" stroke-width="2"/>',
73
- },
74
- mapPin: {
75
- name: "Map Pin / Local",
76
- viewBox: "0 0 24 24",
77
- svgPath: '<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="10" r="3" fill="none" stroke="currentColor" stroke-width="2"/>',
78
- },
79
- euro: {
80
- name: "Euro / Pricing",
81
- viewBox: "0 0 24 24",
82
- svgPath: '<path d="M4 10h12M4 14h9M19 6a7.7 7.7 0 0 0-5.2-2A7.9 7.9 0 0 0 6 12a7.9 7.9 0 0 0 7.8 8 7.7 7.7 0 0 0 5.2-2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
83
- },
84
- arrowRight: {
85
- name: "Arrow Right",
86
- viewBox: "0 0 24 24",
87
- svgPath: '<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><polyline points="12 5 19 12 12 19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
88
- },
89
- };
90
-
91
- /**
92
- * Returns raw inline SVG markup for a standard UI icon.
93
- */
94
- export function renderUiIconSvg(iconName: string, className: string = "w-5 h-5 inline-block"): string {
95
- const icon = UI_ICONS[iconName.toLowerCase()] || UI_ICONS.check;
96
- return `<svg class="${className}" viewBox="${icon.viewBox}" aria-hidden="true">${icon.svgPath}</svg>`;
97
- }
98
-
99
- /**
100
- * Semantically resolves the most relevant UI icon based on feature text keywords.
101
- */
102
- export function resolveFeatureIcon(featureText: string): string {
103
- const lower = featureText.toLowerCase();
104
-
105
- if (lower.includes("secur") || lower.includes("rgpd") || lower.includes("gdpr") || lower.includes("protect") || lower.includes("bank")) {
106
- return "shield";
107
- }
108
- if (lower.includes("sync") || lower.includes("speed") || lower.includes("instant") || lower.includes("real-time") || lower.includes("fast") || lower.includes("rapide")) {
109
- return "zap";
110
- }
111
- if (lower.includes("ai") || lower.includes("ia") || lower.includes("auto") || lower.includes("smart") || lower.includes("intel")) {
112
- return "sparkles";
113
- }
114
- if (lower.includes("chart") || lower.includes("analyt") || lower.includes("report") || lower.includes("stat") || lower.includes("roi") || lower.includes("kpi")) {
115
- return "chart";
116
- }
117
- if (lower.includes("schedul") || lower.includes("calen") || lower.includes("plan") || lower.includes("agenda")) {
118
- return "calendar";
119
- }
120
- if (lower.includes("user") || lower.includes("team") || lower.includes("collab") || lower.includes("client") || lower.includes("contact")) {
121
- return "users";
122
- }
123
- if (lower.includes("price") || lower.includes("tarif") || lower.includes("cost") || lower.includes("devis") || lower.includes("factur")) {
124
- return "euro";
125
- }
126
- if (lower.includes("local") || lower.includes("city") || lower.includes("ville") || lower.includes("map") || lower.includes("gps")) {
127
- return "mapPin";
128
- }
129
- if (lower.includes("24/7") || lower.includes("support") || lower.includes("hour") || lower.includes("time") || lower.includes("temps")) {
130
- return "clock";
131
- }
132
-
133
- return "zap";
134
- }
@@ -1,97 +0,0 @@
1
- /**
2
- * 🔍 Universal URL Analytics & Structural Decomposition Engine
3
- * Inspired by advertools.urlytics
4
- *
5
- * Breaks down any URL into structured path segments, query parameters, directory depths,
6
- * and slug tokens for crawl validation, audit logs, and programmatic matrix matching.
7
- */
8
-
9
- export interface ParsedUrlStructure {
10
- url: string;
11
- scheme: string;
12
- domain: string;
13
- path: string;
14
- depth: number;
15
- dir1?: string;
16
- dir2?: string;
17
- dir3?: string;
18
- lastDir: string;
19
- queryParams: Record<string, string>;
20
- hashFragment?: string;
21
- slugTokens: string[];
22
- charCount: number;
23
- hasTrailingSlash: boolean;
24
- }
25
-
26
- export class UrlyticsEngine {
27
- /**
28
- * Parses a single URL into its granular structural components.
29
- */
30
- static parseUrl(rawUrl: string): ParsedUrlStructure {
31
- if (!rawUrl || typeof rawUrl !== "string") {
32
- return {
33
- url: "",
34
- scheme: "",
35
- domain: "",
36
- path: "/",
37
- depth: 0,
38
- lastDir: "",
39
- queryParams: {},
40
- slugTokens: [],
41
- charCount: 0,
42
- hasTrailingSlash: false,
43
- };
44
- }
45
-
46
- try {
47
- const parsed = new URL(rawUrl.startsWith("http") ? rawUrl : `https://${rawUrl}`);
48
- const pathClean = parsed.pathname.replace(/\/+$/, "");
49
- const segments = pathClean.split("/").filter(Boolean);
50
- const queryParams: Record<string, string> = {};
51
-
52
- parsed.searchParams.forEach((val, key) => {
53
- queryParams[key] = val;
54
- });
55
-
56
- const lastDir = segments.length > 0 ? segments[segments.length - 1] : "";
57
- const slugTokens = lastDir.split("-").filter(Boolean);
58
-
59
- return {
60
- url: rawUrl,
61
- scheme: parsed.protocol.replace(":", ""),
62
- domain: parsed.hostname,
63
- path: parsed.pathname,
64
- depth: segments.length,
65
- dir1: segments[0],
66
- dir2: segments[1],
67
- dir3: segments[2],
68
- lastDir,
69
- queryParams,
70
- hashFragment: parsed.hash ? parsed.hash.replace("#", "") : undefined,
71
- slugTokens,
72
- charCount: rawUrl.length,
73
- hasTrailingSlash: parsed.pathname.length > 1 && parsed.pathname.endsWith("/"),
74
- };
75
- } catch {
76
- return {
77
- url: rawUrl,
78
- scheme: "unknown",
79
- domain: "",
80
- path: rawUrl,
81
- depth: 0,
82
- lastDir: rawUrl,
83
- queryParams: {},
84
- slugTokens: [rawUrl],
85
- charCount: rawUrl.length,
86
- hasTrailingSlash: false,
87
- };
88
- }
89
- }
90
-
91
- /**
92
- * Batch processes an array of URLs for comparative directory analysis.
93
- */
94
- static analyzeUrls(urls: string[]): ParsedUrlStructure[] {
95
- return urls.map((u) => this.parseUrl(u));
96
- }
97
- }
@@ -1,81 +0,0 @@
1
- export interface YouTubeVideoMetadata {
2
- videoId: string;
3
- title: string;
4
- description: string;
5
- uploadDate?: string;
6
- duration?: string; // ISO 8601 e.g. "PT5M30S"
7
- thumbnailUrl?: string;
8
- embedUrl?: string;
9
- hasChapters?: boolean;
10
- chapters?: Array<{ time: number; title: string }>;
11
- }
12
-
13
- export class VideoYouTubeAnalyzer {
14
- /**
15
- * Extracts YouTube Video ID from any standard URL or iframe string
16
- */
17
- static extractVideoId(urlOrIframe: string): string | null {
18
- const regExp = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
19
- const match = urlOrIframe.match(regExp);
20
- return match ? match[1] : null;
21
- }
22
-
23
- /**
24
- * Generates Schema.org VideoObject JSON-LD for rich snippets on Google Video Carousel
25
- */
26
- static generateVideoObjectSchema(meta: YouTubeVideoMetadata) {
27
- const videoId = meta.videoId;
28
- const thumbnail = meta.thumbnailUrl || `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
29
- const embedUrl = meta.embedUrl || `https://www.youtube.com/embed/${videoId}`;
30
-
31
- const schema: Record<string, any> = {
32
- "@context": "https://schema.org",
33
- "@type": "VideoObject",
34
- "name": meta.title,
35
- "description": meta.description,
36
- "thumbnailUrl": [thumbnail],
37
- "uploadDate": meta.uploadDate || new Date().toISOString().split("T")[0],
38
- "embedUrl": embedUrl,
39
- "contentUrl": `https://www.youtube.com/watch?v=${videoId}`
40
- };
41
-
42
- if (meta.duration) {
43
- schema.duration = meta.duration;
44
- }
45
-
46
- if (meta.chapters && meta.chapters.length > 0) {
47
- schema.hasPart = meta.chapters.map(c => ({
48
- "@type": "Clip",
49
- "name": c.title,
50
- "startOffset": c.time,
51
- "url": `https://www.youtube.com/watch?v=${videoId}&t=${c.time}s`
52
- }));
53
- }
54
-
55
- return schema;
56
- }
57
-
58
- /**
59
- * Generates responsive, lazy-loaded, SEO-optimized HTML embed with Schema markup
60
- */
61
- static generateSeoVideoEmbedHtml(meta: YouTubeVideoMetadata): string {
62
- const videoId = meta.videoId;
63
- const jsonLd = JSON.stringify(this.generateVideoObjectSchema(meta));
64
-
65
- return `
66
- <div class="lynxseo-video-wrapper" style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:12px;box-shadow:0 4px 12px rgba(0,0,0,0.08);margin:24px 0;">
67
- <iframe
68
- src="https://www.youtube.com/embed/${videoId}"
69
- title="${meta.title.replace(/"/g, '&quot;')}"
70
- style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
71
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
72
- allowfullscreen
73
- loading="lazy">
74
- </iframe>
75
- </div>
76
- <script type="application/ld+json">
77
- ${jsonLd}
78
- </script>
79
- `.trim();
80
- }
81
- }
@@ -1,161 +0,0 @@
1
- /**
2
- * 🟢 Yoast SEO Parity & Real-Time Readability Engine
3
- *
4
- * Implements the iconic Yoast SEO features (13M+ installs):
5
- * 1. Readability Analysis (Analyse de Lisibilité aux pastilles Vertes/Oranges/Rouges) :
6
- * - Flesch Reading Ease Formula (Score de lisibilité)
7
- * - Transition Words Detection (Mots de transition > 30%)
8
- * - Passive Voice Detection (Voix passive < 10%)
9
- * - Sentence Length Distribution (Phrases > 20 mots < 25%)
10
- * - Paragraph Length (< 150 mots par paragraphe)
11
- * - Subheading Distribution (< 300 mots sous un sous-titre)
12
- * 2. Google SERP Pixel Width Calculator (Titre max 600px, Description max 960px)
13
- * 3. Cornerstone Content (Contenu Pilier & Priorisation du Maillage)
14
- */
15
-
16
- export interface YoastReadabilityBullet {
17
- id: string;
18
- label: string;
19
- status: "green" | "orange" | "red"; // Pastille verte, orange ou rouge
20
- scoreValue: number;
21
- message: string;
22
- }
23
-
24
- export interface YoastAnalysisResult {
25
- overallReadability: "green" | "orange" | "red";
26
- fleschReadingScore: number; // 0 to 100 (60-70 = standard/facile)
27
- fleschGrade: string;
28
- transitionWordsPercent: number;
29
- passiveVoicePercent: number;
30
- longSentencesPercent: number;
31
- bullets: YoastReadabilityBullet[];
32
- serpPreview: {
33
- titlePixelWidth: number;
34
- titlePixelLimit: 600;
35
- titleIsTruncated: boolean;
36
- descPixelWidth: number;
37
- descPixelLimit: 960;
38
- descIsTruncated: boolean;
39
- };
40
- }
41
-
42
- export class YoastParityEngine {
43
- private static frenchTransitionWords = [
44
- "en effet", "de plus", "par conséquent", "cependant", "ainsi", "notamment", "en outre",
45
- "d'ailleurs", "toutefois", "en revanche", "néanmoins", "c'est pourquoi", "en conclusion",
46
- "par exemple", "premièrement", "deuxièmement", "en fait", "grâce à", "donc", "car", "puisque"
47
- ];
48
-
49
- /**
50
- * 1. Full Yoast-Style Readability Analysis.
51
- */
52
- static analyzeReadability(text: string, title = "", description = ""): YoastAnalysisResult {
53
- const plainText = text.replace(/<[^>]+>/g, " ").replace(/[#*`_~]/g, " ").trim();
54
- const sentences = plainText.split(/[.!?]+/).map((s) => s.trim()).filter((s) => s.length > 0);
55
- const words = plainText.split(/\s+/).filter((w) => w.length > 0);
56
- const paragraphs = text.split(/\n\s*\n/).map((p) => p.trim()).filter((p) => p.length > 0);
57
-
58
- const totalWords = Math.max(1, words.length);
59
- const totalSentences = Math.max(1, sentences.length);
60
-
61
- // 1. Flesch Reading Ease (Formule adaptée au français : 207 - (1.015 * ASL) - (84.6 * ASW))
62
- const avgSentenceLength = totalWords / totalSentences;
63
- // Estimation des syllabes (~1.4 syllabes par mot en français moyen)
64
- const estimatedSyllables = words.reduce((acc, word) => acc + Math.max(1, word.length / 3), 0);
65
- const avgSyllablesPerWord = estimatedSyllables / totalWords;
66
- const fleschScore = Math.max(0, Math.min(100, Math.round(207 - (1.015 * avgSentenceLength) - (84.6 * avgSyllablesPerWord))));
67
-
68
- let fleschGrade = "Très facile à lire";
69
- if (fleschScore < 30) fleschGrade = "Très difficile (académique)";
70
- else if (fleschScore < 50) fleschGrade = "Difficile";
71
- else if (fleschScore < 60) fleschGrade = "Moyen";
72
- else if (fleschScore < 70) fleschGrade = "Facile (Idéal pour le web)";
73
-
74
- // 2. Transition Words Detection (> 30% des phrases)
75
- let transitionCount = 0;
76
- for (const sentence of sentences) {
77
- const lower = sentence.toLowerCase();
78
- if (this.frenchTransitionWords.some((tw) => lower.includes(tw))) {
79
- transitionCount++;
80
- }
81
- }
82
- const transitionPercent = Math.round((transitionCount / totalSentences) * 100);
83
-
84
- // 3. Long Sentences (> 20 mots)
85
- const longSentences = sentences.filter((s) => s.split(/\s+/).length > 20).length;
86
- const longSentencesPercent = Math.round((longSentences / totalSentences) * 100);
87
-
88
- // 4. Passive Voice Approximation (formules passives en français : être + participe)
89
- const passiveRegex = /\b(est|sont|été|fut|furent|sera|seront|était|étaient)\s+([a-zéèêëàâîïôûüç]+(é|és|ée|ées|i|is|ie|ies|u|us|ue|ues))\b/gi;
90
- const passiveMatches = plainText.match(passiveRegex) || [];
91
- const passivePercent = Math.round((passiveMatches.length / totalSentences) * 100);
92
-
93
- // Bullets checklist (Pastilles Yoast)
94
- const bullets: YoastReadabilityBullet[] = [];
95
-
96
- // Bullet Flesch
97
- bullets.push({
98
- id: "flesch_reading_ease",
99
- label: "Facilité de lecture Flesch",
100
- status: fleschScore >= 55 ? "green" : fleschScore >= 45 ? "orange" : "red",
101
- scoreValue: fleschScore,
102
- message: `Score Flesch : ${fleschScore}/100 (${fleschGrade}).`,
103
- });
104
-
105
- // Bullet Transitions
106
- bullets.push({
107
- id: "transition_words",
108
- label: "Mots de transition",
109
- status: transitionPercent >= 30 ? "green" : transitionPercent >= 20 ? "orange" : "red",
110
- scoreValue: transitionPercent,
111
- message: `${transitionPercent}% des phrases contiennent des mots de transition (Recommandé : > 30%).`,
112
- });
113
-
114
- // Bullet Sentence Length
115
- bullets.push({
116
- id: "sentence_length",
117
- label: "Longueur des phrases",
118
- status: longSentencesPercent <= 25 ? "green" : longSentencesPercent <= 35 ? "orange" : "red",
119
- scoreValue: longSentencesPercent,
120
- message: `${longSentencesPercent}% des phrases dépassent 20 mots (Recommandé : < 25%).`,
121
- });
122
-
123
- // Bullet Passive Voice
124
- bullets.push({
125
- id: "passive_voice",
126
- label: "Voix passive",
127
- status: passivePercent <= 10 ? "green" : passivePercent <= 18 ? "orange" : "red",
128
- scoreValue: passivePercent,
129
- message: `${passivePercent}% de voix passive (Recommandé : < 10%).`,
130
- });
131
-
132
- // Overall status
133
- const redCount = bullets.filter((b) => b.status === "red").length;
134
- const orangeCount = bullets.filter((b) => b.status === "orange").length;
135
- let overallReadability: "green" | "orange" | "red" = "green";
136
- if (redCount >= 2) overallReadability = "red";
137
- else if (redCount === 1 || orangeCount >= 2) overallReadability = "orange";
138
-
139
- // 5. SERP Pixel Width (Approximation : ~9.5px par caractère pour le titre Arial 18px)
140
- const titlePixelWidth = Math.round(title.length * 9.2);
141
- const descPixelWidth = Math.round(description.length * 5.8);
142
-
143
- return {
144
- overallReadability,
145
- fleschReadingScore: fleschScore,
146
- fleschGrade,
147
- transitionWordsPercent: transitionPercent,
148
- passiveVoicePercent: passivePercent,
149
- longSentencesPercent: longSentencesPercent,
150
- bullets,
151
- serpPreview: {
152
- titlePixelWidth,
153
- titlePixelLimit: 600,
154
- titleIsTruncated: titlePixelWidth > 600,
155
- descPixelWidth,
156
- descPixelLimit: 960,
157
- descIsTruncated: descPixelWidth > 960,
158
- },
159
- };
160
- }
161
- }
package/tsconfig.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "declaration": true,
7
- "emitDeclarationOnly": true,
8
- "rootDir": "./src",
9
- "outDir": "./dist",
10
- "strict": true,
11
- "skipLibCheck": true
12
- },
13
- "include": ["src/**/*"],
14
- "exclude": ["src/**/*.test.ts"]
15
- }