@consilioweb/payload-seo-analyzer 1.7.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.
- package/LICENSE +21 -0
- package/README.md +1201 -0
- package/dist/client.cjs +19286 -0
- package/dist/client.d.cts +133 -0
- package/dist/client.d.ts +133 -0
- package/dist/client.js +19261 -0
- package/dist/index.cjs +11836 -0
- package/dist/index.d.cts +1416 -0
- package/dist/index.d.ts +1416 -0
- package/dist/index.js +11752 -0
- package/dist/views.cjs +216 -0
- package/dist/views.d.cts +67 -0
- package/dist/views.d.ts +67 -0
- package/dist/views.js +206 -0
- package/package.json +122 -0
- package/scripts/uninstall.mjs +282 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,1416 @@
|
|
|
1
|
+
import { Field, Plugin, Payload, PayloadHandler, CollectionConfig, CollectionAfterChangeHook } from 'payload';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SEO Analyzer — Type definitions.
|
|
5
|
+
* Shared across all rule modules and the main orchestrator.
|
|
6
|
+
*/
|
|
7
|
+
type CheckStatus = 'pass' | 'warning' | 'fail';
|
|
8
|
+
type CheckCategory = 'critical' | 'important' | 'bonus';
|
|
9
|
+
/** Rule group label — used by the UI to group checks by topic */
|
|
10
|
+
type RuleGroup = 'title' | 'meta-description' | 'url' | 'headings' | 'content' | 'images' | 'linking' | 'social' | 'schema' | 'readability' | 'quality' | 'secondary-keywords' | 'cornerstone' | 'freshness' | 'technical' | 'accessibility' | 'ecommerce';
|
|
11
|
+
interface SeoCheck {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
status: CheckStatus;
|
|
15
|
+
message: string;
|
|
16
|
+
category: CheckCategory;
|
|
17
|
+
weight: number;
|
|
18
|
+
/** Which rule group this check belongs to */
|
|
19
|
+
group: RuleGroup;
|
|
20
|
+
/** Actionable tip displayed below the message when status is not 'pass' */
|
|
21
|
+
tip?: string;
|
|
22
|
+
}
|
|
23
|
+
type SeoLevel = 'poor' | 'ok' | 'good' | 'excellent';
|
|
24
|
+
interface SeoAnalysis {
|
|
25
|
+
score: number;
|
|
26
|
+
level: SeoLevel;
|
|
27
|
+
checks: SeoCheck[];
|
|
28
|
+
}
|
|
29
|
+
interface SeoInput {
|
|
30
|
+
metaTitle?: string;
|
|
31
|
+
metaDescription?: string;
|
|
32
|
+
metaImage?: unknown;
|
|
33
|
+
slug?: string;
|
|
34
|
+
focusKeyword?: string;
|
|
35
|
+
/** Secondary focus keywords (in addition to the primary keyword) */
|
|
36
|
+
focusKeywords?: string[];
|
|
37
|
+
heroTitle?: string;
|
|
38
|
+
heroRichText?: unknown;
|
|
39
|
+
heroLinks?: unknown[];
|
|
40
|
+
heroMedia?: unknown;
|
|
41
|
+
blocks?: unknown[];
|
|
42
|
+
/** For posts — the Lexical content field */
|
|
43
|
+
content?: unknown;
|
|
44
|
+
/** Whether the content is a post (for word count thresholds) */
|
|
45
|
+
isPost?: boolean;
|
|
46
|
+
/** Whether the content is a product (triggers e-commerce SEO checks) */
|
|
47
|
+
isProduct?: boolean;
|
|
48
|
+
/** Whether the content is marked as cornerstone/pillar content */
|
|
49
|
+
isCornerstone?: boolean;
|
|
50
|
+
/** ISO date string — last time the document was updated */
|
|
51
|
+
updatedAt?: string;
|
|
52
|
+
/** ISO date string — last time the content was manually reviewed */
|
|
53
|
+
contentLastReviewed?: string;
|
|
54
|
+
/** Canonical URL for this page (if explicitly set) */
|
|
55
|
+
canonicalUrl?: string;
|
|
56
|
+
/** Robots meta directives (e.g. 'noindex', 'nofollow', 'noindex, nofollow') */
|
|
57
|
+
robotsMeta?: string;
|
|
58
|
+
/** Whether this content is a Payload Global (disables URL/slug checks) */
|
|
59
|
+
isGlobal?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** Page type for adapting SEO rule severity */
|
|
62
|
+
type PageType = 'legal' | 'contact' | 'form' | 'home' | 'service' | 'local-seo' | 'blog' | 'agency' | 'resource' | 'generic';
|
|
63
|
+
/**
|
|
64
|
+
* Overridable thresholds for the SEO analyzer.
|
|
65
|
+
* All fields are optional — defaults from constants.ts are used when omitted.
|
|
66
|
+
*/
|
|
67
|
+
interface SeoThresholds {
|
|
68
|
+
titleLengthMin?: number;
|
|
69
|
+
titleLengthMax?: number;
|
|
70
|
+
metaDescLengthMin?: number;
|
|
71
|
+
metaDescLengthMax?: number;
|
|
72
|
+
minWordsGeneric?: number;
|
|
73
|
+
minWordsPost?: number;
|
|
74
|
+
keywordDensityMin?: number;
|
|
75
|
+
keywordDensityMax?: number;
|
|
76
|
+
fleschScorePass?: number;
|
|
77
|
+
slugMaxLength?: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Configuration for the SEO analyzer.
|
|
81
|
+
* All fields are optional — sensible defaults are used when omitted.
|
|
82
|
+
* Use this to adapt the analyzer to different projects/sites.
|
|
83
|
+
*/
|
|
84
|
+
interface SeoConfig {
|
|
85
|
+
/** Additional local SEO slugs to recognize (appended to pattern detection) */
|
|
86
|
+
localSeoSlugs?: string[];
|
|
87
|
+
/** Regex pattern for detecting local-SEO pages (checked in addition to slug list) */
|
|
88
|
+
localSeoPattern?: RegExp;
|
|
89
|
+
/** Additional stop word compounds for slug analysis (appended to defaults) */
|
|
90
|
+
stopWordCompounds?: Array<readonly [string, string]>;
|
|
91
|
+
/** Maximum recursion depth for Lexical tree extraction (default: 50) */
|
|
92
|
+
maxRecursionDepth?: number;
|
|
93
|
+
/** Base URL of the site (used for canonical URL validation) */
|
|
94
|
+
siteUrl?: string;
|
|
95
|
+
/** Site name (used for brand duplicate check in titles) */
|
|
96
|
+
siteName?: string;
|
|
97
|
+
/** Rule groups to disable entirely */
|
|
98
|
+
disabledRules?: RuleGroup[];
|
|
99
|
+
/** Override the weight of all checks within a rule group */
|
|
100
|
+
overrideWeights?: Partial<Record<RuleGroup, number>>;
|
|
101
|
+
/** Custom thresholds (override defaults from constants.ts) */
|
|
102
|
+
thresholds?: SeoThresholds;
|
|
103
|
+
/** Locale for language-specific analysis (default: 'fr') */
|
|
104
|
+
locale?: 'fr' | 'en';
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Granular feature flags for the SEO plugin.
|
|
108
|
+
* All features default to `true`. Set a feature to `false` to disable it
|
|
109
|
+
* and avoid loading its collections, endpoints, and admin views.
|
|
110
|
+
*
|
|
111
|
+
* The core analyzer sidebar, validate endpoint, and meta fields are always active.
|
|
112
|
+
*/
|
|
113
|
+
interface SeoFeatures {
|
|
114
|
+
/** Core SEO analyzer sidebar — always enabled, cannot be disabled */
|
|
115
|
+
analyzer?: boolean;
|
|
116
|
+
/** Dashboard view with overview of all pages (/admin/seo) */
|
|
117
|
+
dashboard?: boolean;
|
|
118
|
+
/** Redirect manager (collection + CRUD endpoints + view) */
|
|
119
|
+
redirects?: boolean;
|
|
120
|
+
/** Performance / GSC import (collection + endpoints + view) */
|
|
121
|
+
performance?: boolean;
|
|
122
|
+
/** Link graph visualization (endpoint + view) */
|
|
123
|
+
linkGraph?: boolean;
|
|
124
|
+
/** Keyword research (endpoint + view) */
|
|
125
|
+
keywords?: boolean;
|
|
126
|
+
/** Cannibalization detection (endpoint + view) */
|
|
127
|
+
cannibalization?: boolean;
|
|
128
|
+
/** Schema.org / JSON-LD builder (endpoint + view) */
|
|
129
|
+
schemaBuilder?: boolean;
|
|
130
|
+
/** Sitemap audit (endpoint + view) */
|
|
131
|
+
sitemapAudit?: boolean;
|
|
132
|
+
/** SEO Logs — 404 tracking (collection + endpoints) */
|
|
133
|
+
seoLogs?: boolean;
|
|
134
|
+
/** Score history tracking (collection + hook + endpoint) */
|
|
135
|
+
scoreHistory?: boolean;
|
|
136
|
+
/** External links checker (endpoint) */
|
|
137
|
+
externalLinks?: boolean;
|
|
138
|
+
/** AI content generation/rewrite (endpoints) */
|
|
139
|
+
aiFeatures?: boolean;
|
|
140
|
+
/** Duplicate content detection (endpoint) */
|
|
141
|
+
duplicateContent?: boolean;
|
|
142
|
+
/** Settings view (/admin/seo-config) */
|
|
143
|
+
settings?: boolean;
|
|
144
|
+
}
|
|
145
|
+
/** Pre-computed context shared across all rule modules to avoid redundant work */
|
|
146
|
+
interface AnalysisContext {
|
|
147
|
+
/** All plain text extracted from the page */
|
|
148
|
+
fullText: string;
|
|
149
|
+
/** Word count of fullText */
|
|
150
|
+
wordCount: number;
|
|
151
|
+
/** Normalised focus keyword (lowercase, trimmed) — empty string if none */
|
|
152
|
+
normalizedKeyword: string;
|
|
153
|
+
/** Normalised secondary keywords (lowercase, trimmed, duplicates removed) */
|
|
154
|
+
secondaryNormalizedKeywords: string[];
|
|
155
|
+
/** All headings found in the content */
|
|
156
|
+
allHeadings: Array<{
|
|
157
|
+
tag: string;
|
|
158
|
+
text: string;
|
|
159
|
+
}>;
|
|
160
|
+
/** All links found in the content (url + anchor text) */
|
|
161
|
+
allLinks: Array<{
|
|
162
|
+
url: string;
|
|
163
|
+
text: string;
|
|
164
|
+
}>;
|
|
165
|
+
/** Image statistics from blocks + content */
|
|
166
|
+
imageStats: {
|
|
167
|
+
total: number;
|
|
168
|
+
withAlt: number;
|
|
169
|
+
altTexts: string[];
|
|
170
|
+
};
|
|
171
|
+
/** All sentences extracted from fullText */
|
|
172
|
+
sentences: string[];
|
|
173
|
+
/** Whether the content is a blog post (affects thresholds) */
|
|
174
|
+
isPost: boolean;
|
|
175
|
+
/** Detected page type for adapting rule severity */
|
|
176
|
+
pageType: PageType;
|
|
177
|
+
/** SEO configuration (merged defaults + user overrides) */
|
|
178
|
+
config: SeoConfig;
|
|
179
|
+
/** Active locale for language-specific rules */
|
|
180
|
+
locale: 'fr' | 'en';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* SEO Analyzer — Helper utilities.
|
|
185
|
+
* Pure functions for extracting data from Lexical JSON, counting words/syllables,
|
|
186
|
+
* and performing French-language readability analysis.
|
|
187
|
+
*/
|
|
188
|
+
/**
|
|
189
|
+
* Strip diacritical marks (accents) from text for comparison.
|
|
190
|
+
* "Réalité augmentée" → "realite augmentee"
|
|
191
|
+
* Used to avoid false negatives when comparing keywords against content.
|
|
192
|
+
*/
|
|
193
|
+
declare function normalizeForComparison(text: string): string;
|
|
194
|
+
/**
|
|
195
|
+
* Slugify a keyword for URL comparison.
|
|
196
|
+
* Strips accents, special chars (.&/), and converts spaces to hyphens.
|
|
197
|
+
* "Next.js & React" → "nextjs-react"
|
|
198
|
+
* "design UX/UI" → "design-ux-ui"
|
|
199
|
+
*/
|
|
200
|
+
declare function slugifyKeyword(kw: string): string;
|
|
201
|
+
/**
|
|
202
|
+
* Smart keyword matching adapted for French.
|
|
203
|
+
*
|
|
204
|
+
* For 1-2 word keywords: exact substring match (accent-insensitive).
|
|
205
|
+
* For 3+ word keywords: all significant words (>3 chars) must be present
|
|
206
|
+
* in the text. This handles French articles/prepositions that naturally
|
|
207
|
+
* separate keyword components ("politique de confidentialité" vs
|
|
208
|
+
* "politique confidentialité RGPD").
|
|
209
|
+
*
|
|
210
|
+
* Both `keyword` and `text` must be pre-normalized with `normalizeForComparison()`.
|
|
211
|
+
*/
|
|
212
|
+
declare function keywordMatchesText(normalizedKeyword: string, normalizedText: string): boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Count keyword occurrences in text with smart French matching.
|
|
215
|
+
*
|
|
216
|
+
* Returns exact occurrence count plus a word-level match flag for multi-word
|
|
217
|
+
* keywords where individual significant words appear but not as exact phrase.
|
|
218
|
+
* Also returns an estimated effective density.
|
|
219
|
+
*/
|
|
220
|
+
declare function countKeywordOccurrences(normalizedKeyword: string, normalizedText: string, totalWordCount: number): {
|
|
221
|
+
exactCount: number;
|
|
222
|
+
wordLevelMatch: boolean;
|
|
223
|
+
effectiveDensity: number;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Detect the page type from the slug (and optionally the collection) to adapt
|
|
228
|
+
* SEO rule severity. Unified version used by both the analyzer and AI prompts.
|
|
229
|
+
*
|
|
230
|
+
* Priority order:
|
|
231
|
+
* 1. Collection-based detection (posts → blog)
|
|
232
|
+
* 2. Exact slug matches (home, legal)
|
|
233
|
+
* 3. Pattern-based detection (local-seo, service, resource, agency, blog)
|
|
234
|
+
* 4. Fallback → generic
|
|
235
|
+
*
|
|
236
|
+
* @param extraLocalSeoSlugs Additional local SEO slugs from SeoConfig
|
|
237
|
+
*/
|
|
238
|
+
declare function detectPageType(slug: string, collection?: string, extraLocalSeoSlugs?: string[], locale?: 'fr' | 'en'): PageType;
|
|
239
|
+
/**
|
|
240
|
+
* Recursively extract plain text from a Lexical JSON tree.
|
|
241
|
+
* @param maxDepth Maximum recursion depth to prevent stack overflow (default: 50)
|
|
242
|
+
*/
|
|
243
|
+
declare function extractTextFromLexical(node: unknown, maxDepth?: number): string;
|
|
244
|
+
/**
|
|
245
|
+
* Extract headings from a Lexical JSON tree.
|
|
246
|
+
* Returns an array of { tag: 'h1' | 'h2' | ..., text: string }
|
|
247
|
+
* @param maxDepth Maximum recursion depth to prevent stack overflow (default: 50)
|
|
248
|
+
*/
|
|
249
|
+
declare function extractHeadingsFromLexical(node: unknown, maxDepth?: number): Array<{
|
|
250
|
+
tag: string;
|
|
251
|
+
text: string;
|
|
252
|
+
}>;
|
|
253
|
+
/**
|
|
254
|
+
* Extract links from a Lexical JSON tree.
|
|
255
|
+
* Returns an array of { url: string, text: string }
|
|
256
|
+
* @param maxDepth Maximum recursion depth to prevent stack overflow (default: 50)
|
|
257
|
+
*/
|
|
258
|
+
declare function extractLinksFromLexical(node: unknown, maxDepth?: number): Array<{
|
|
259
|
+
url: string;
|
|
260
|
+
text: string;
|
|
261
|
+
}>;
|
|
262
|
+
/**
|
|
263
|
+
* Extract link URLs from Lexical (flat array — backward compat).
|
|
264
|
+
*/
|
|
265
|
+
declare function extractLinkUrlsFromLexical(node: unknown): string[];
|
|
266
|
+
/**
|
|
267
|
+
* Extract images from a Lexical JSON tree.
|
|
268
|
+
* @param maxDepth Maximum recursion depth to prevent stack overflow (default: 50)
|
|
269
|
+
*/
|
|
270
|
+
declare function extractImagesFromLexical(node: unknown, maxDepth?: number): {
|
|
271
|
+
total: number;
|
|
272
|
+
withAlt: number;
|
|
273
|
+
altTexts: string[];
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Check images in blocks (MediaBlock, Content columns, etc.).
|
|
277
|
+
*/
|
|
278
|
+
declare function checkImagesInBlocks(blocks: unknown[]): {
|
|
279
|
+
total: number;
|
|
280
|
+
withAlt: number;
|
|
281
|
+
altTexts: string[];
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Count words in a string.
|
|
285
|
+
*/
|
|
286
|
+
declare function countWords(text: string): number;
|
|
287
|
+
/**
|
|
288
|
+
* Split text into sentences (French/English-aware).
|
|
289
|
+
* Handles common abbreviations (M., Mme., Mr., Mrs., etc.) to avoid false splits.
|
|
290
|
+
*/
|
|
291
|
+
declare function countSentences(text: string, locale?: 'fr' | 'en'): string[];
|
|
292
|
+
/**
|
|
293
|
+
* Count syllables in a French word (Kandel-Moles approximation).
|
|
294
|
+
*
|
|
295
|
+
* Rules:
|
|
296
|
+
* 1. Count vowel groups (a, e, i, o, u, y, accented variants)
|
|
297
|
+
* 2. Silent final 'e' does not count (except single-syllable words)
|
|
298
|
+
* 3. Common diphthongs (eau, ou, ai, ei, au, oi, eu) count as 1 vowel group
|
|
299
|
+
*/
|
|
300
|
+
declare function countSyllablesFR(word: string): number;
|
|
301
|
+
/**
|
|
302
|
+
* Count syllables in an English word.
|
|
303
|
+
* Handles silent-e, -ed/-es endings, common exceptions.
|
|
304
|
+
*/
|
|
305
|
+
declare function countSyllablesEN(word: string): number;
|
|
306
|
+
/**
|
|
307
|
+
* Calculate Flesch reading ease adapted for French (Kandel-Moles formula).
|
|
308
|
+
* Flesch FR = 207 - 1.015 * (words/sentences) - 73.6 * (syllables/words)
|
|
309
|
+
*
|
|
310
|
+
* Returns a score from 0 (very difficult) to ~100 (very easy).
|
|
311
|
+
*/
|
|
312
|
+
declare function calculateFleschFR(text: string): number;
|
|
313
|
+
/**
|
|
314
|
+
* Calculate Flesch reading ease — bilingual.
|
|
315
|
+
* FR: Kandel-Moles (207 - 1.015xASL - 73.6xASW)
|
|
316
|
+
* EN: Flesch-Kincaid (206.835 - 1.015xASL - 84.6xASW)
|
|
317
|
+
*/
|
|
318
|
+
declare function calculateFlesch(text: string, locale?: 'fr' | 'en'): number;
|
|
319
|
+
/**
|
|
320
|
+
* Detect passive voice in a French sentence.
|
|
321
|
+
* Looks for "être" conjugations followed by a past participle,
|
|
322
|
+
* while excluding common passé composé verbs (aller, venir, arriver, etc.)
|
|
323
|
+
* that use "être" as auxiliary but are NOT passive voice.
|
|
324
|
+
*
|
|
325
|
+
* Also excludes state descriptions ("est basé à", "est situé à") that are
|
|
326
|
+
* natural in French B2B copy and not true passive constructions.
|
|
327
|
+
*/
|
|
328
|
+
declare function detectPassiveVoice(sentence: string, locale?: 'fr' | 'en'): boolean;
|
|
329
|
+
/**
|
|
330
|
+
* Check whether a sentence contains a transition word/phrase.
|
|
331
|
+
*/
|
|
332
|
+
declare function hasTransitionWord(sentence: string, locale?: 'fr' | 'en'): boolean;
|
|
333
|
+
/**
|
|
334
|
+
* Get the list of French stop words (for slug analysis).
|
|
335
|
+
*/
|
|
336
|
+
declare function getStopWordsFR(): readonly string[];
|
|
337
|
+
/**
|
|
338
|
+
* Compound expressions in French where stop words are meaningful.
|
|
339
|
+
* These should NOT be flagged in slug analysis.
|
|
340
|
+
* Checks if a stop word at position `idx` in slug parts is part of a known expression.
|
|
341
|
+
*/
|
|
342
|
+
declare function isStopWordInCompoundExpression(parts: string[], idx: number, extraCompounds?: Array<readonly [string, string]>): boolean;
|
|
343
|
+
/**
|
|
344
|
+
* French action verbs commonly used in CTAs.
|
|
345
|
+
*/
|
|
346
|
+
declare function getActionVerbsFR(): readonly string[];
|
|
347
|
+
/**
|
|
348
|
+
* Check for long sections (>N words without a heading break).
|
|
349
|
+
* Returns the number of sections that exceed the threshold.
|
|
350
|
+
*/
|
|
351
|
+
declare function countLongSections(node: unknown, threshold?: number): number;
|
|
352
|
+
/**
|
|
353
|
+
* Extract lists (ol/ul) from a Lexical JSON tree.
|
|
354
|
+
* Returns an array of { listType: 'bullet' | 'number', items: number }.
|
|
355
|
+
*/
|
|
356
|
+
declare function extractListsFromLexical(node: unknown, maxDepth?: number): Array<{
|
|
357
|
+
listType: string;
|
|
358
|
+
items: number;
|
|
359
|
+
}>;
|
|
360
|
+
/**
|
|
361
|
+
* Check heading hierarchy: no level skipping (e.g. h2 -> h4 without h3).
|
|
362
|
+
*/
|
|
363
|
+
declare function checkHeadingHierarchy(headings: Array<{
|
|
364
|
+
tag: string;
|
|
365
|
+
text: string;
|
|
366
|
+
}>): boolean;
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Dashboard i18n — FR/EN translations for all dashboard UI components.
|
|
370
|
+
* Provides a simple, pragmatic translation system for the SEO Analyzer dashboard.
|
|
371
|
+
*/
|
|
372
|
+
type DashboardLocale = 'fr' | 'en' | (string & {});
|
|
373
|
+
interface DashboardTranslations {
|
|
374
|
+
common: {
|
|
375
|
+
loading: string;
|
|
376
|
+
loadingError: string;
|
|
377
|
+
retry: string;
|
|
378
|
+
save: string;
|
|
379
|
+
saving: string;
|
|
380
|
+
cancel: string;
|
|
381
|
+
refresh: string;
|
|
382
|
+
exportCsv: string;
|
|
383
|
+
exportJson: string;
|
|
384
|
+
exportPdf: string;
|
|
385
|
+
edit: string;
|
|
386
|
+
delete: string;
|
|
387
|
+
add: string;
|
|
388
|
+
noResults: string;
|
|
389
|
+
previous: string;
|
|
390
|
+
next: string;
|
|
391
|
+
page: string;
|
|
392
|
+
selected: string;
|
|
393
|
+
deselectAll: string;
|
|
394
|
+
characters: string;
|
|
395
|
+
generate: string;
|
|
396
|
+
generating: string;
|
|
397
|
+
copy: string;
|
|
398
|
+
serverError: string;
|
|
399
|
+
networkError: string;
|
|
400
|
+
noTitle: string;
|
|
401
|
+
none: string;
|
|
402
|
+
article: string;
|
|
403
|
+
ok: string;
|
|
404
|
+
modify: string;
|
|
405
|
+
};
|
|
406
|
+
nav: {
|
|
407
|
+
dashboard: string;
|
|
408
|
+
sitemapAudit: string;
|
|
409
|
+
redirects: string;
|
|
410
|
+
cannibalization: string;
|
|
411
|
+
performance: string;
|
|
412
|
+
keywords: string;
|
|
413
|
+
schemaOrg: string;
|
|
414
|
+
linkGraph: string;
|
|
415
|
+
settings: string;
|
|
416
|
+
seo: string;
|
|
417
|
+
};
|
|
418
|
+
seoView: {
|
|
419
|
+
loadingAudit: string;
|
|
420
|
+
errorSaving: string;
|
|
421
|
+
auditTitle: string;
|
|
422
|
+
pagesAnalyzed: string;
|
|
423
|
+
markCornerstone: string;
|
|
424
|
+
unmarkCornerstone: string;
|
|
425
|
+
searchPlaceholder: string;
|
|
426
|
+
allCollections: string;
|
|
427
|
+
allScores: string;
|
|
428
|
+
goodScores: string;
|
|
429
|
+
needsWork: string;
|
|
430
|
+
criticalScores: string;
|
|
431
|
+
missingMeta: string;
|
|
432
|
+
missingH1: string;
|
|
433
|
+
lowReadability: string;
|
|
434
|
+
averageScore: string;
|
|
435
|
+
goodLabel: string;
|
|
436
|
+
needsWorkLabel: string;
|
|
437
|
+
criticalLabel: string;
|
|
438
|
+
noKeyword: string;
|
|
439
|
+
wordsAvg: string;
|
|
440
|
+
metaTitle: string;
|
|
441
|
+
metaDesc: string;
|
|
442
|
+
cornerstone: string;
|
|
443
|
+
collection: string;
|
|
444
|
+
score: string;
|
|
445
|
+
keyword: string;
|
|
446
|
+
h1: string;
|
|
447
|
+
og: string;
|
|
448
|
+
internal: string;
|
|
449
|
+
external: string;
|
|
450
|
+
words: string;
|
|
451
|
+
readability: string;
|
|
452
|
+
updated: string;
|
|
453
|
+
seoReport: string;
|
|
454
|
+
overview: string;
|
|
455
|
+
missingMetaTitle: string;
|
|
456
|
+
missingMetaDescription: string;
|
|
457
|
+
missingKeyword: string;
|
|
458
|
+
shortContent: string;
|
|
459
|
+
lowReadabilityIssue: string;
|
|
460
|
+
missingOgImage: string;
|
|
461
|
+
noInternalLinks: string;
|
|
462
|
+
lowOverallScore: string;
|
|
463
|
+
shortMetaTitle: string;
|
|
464
|
+
longMetaTitle: string;
|
|
465
|
+
shortMetaDesc: string;
|
|
466
|
+
longMetaDesc: string;
|
|
467
|
+
top5PriorityActions: string;
|
|
468
|
+
perPageDetails: string;
|
|
469
|
+
title: string;
|
|
470
|
+
slug: string;
|
|
471
|
+
issues: string;
|
|
472
|
+
identifiedIssues: string;
|
|
473
|
+
scoreDistribution: string;
|
|
474
|
+
good: string;
|
|
475
|
+
critical: string;
|
|
476
|
+
generatedBy: string;
|
|
477
|
+
resultsDisplayed: string;
|
|
478
|
+
noKeywordLabel: string;
|
|
479
|
+
wordsAverage: string;
|
|
480
|
+
readabilityAvg: string;
|
|
481
|
+
noTitleDesc: string;
|
|
482
|
+
previousLabel: string;
|
|
483
|
+
};
|
|
484
|
+
sitemapAudit: {
|
|
485
|
+
loading404: string;
|
|
486
|
+
title: string;
|
|
487
|
+
totalPages: string;
|
|
488
|
+
internalLinks: string;
|
|
489
|
+
linksPerPage: string;
|
|
490
|
+
average: string;
|
|
491
|
+
orphaned: string;
|
|
492
|
+
fragile: string;
|
|
493
|
+
brokenLinks: string;
|
|
494
|
+
searchPlaceholder: string;
|
|
495
|
+
orphanedPages: string;
|
|
496
|
+
fragilePages: string;
|
|
497
|
+
linkHubs: string;
|
|
498
|
+
logs404: string;
|
|
499
|
+
externalLinks: string;
|
|
500
|
+
noOrphanedPages: string;
|
|
501
|
+
orphanedPagesDesc: string;
|
|
502
|
+
zeroIncomingLinks: string;
|
|
503
|
+
noFragilePages: string;
|
|
504
|
+
fragilePagesDesc: string;
|
|
505
|
+
oneIncomingLink: string;
|
|
506
|
+
from: string;
|
|
507
|
+
anchor: string;
|
|
508
|
+
noLinkHubs: string;
|
|
509
|
+
linkHubsDesc: string;
|
|
510
|
+
links: string;
|
|
511
|
+
brokenLinksDesc: string;
|
|
512
|
+
created: string;
|
|
513
|
+
errors: string;
|
|
514
|
+
selectedItems: string;
|
|
515
|
+
selectAll: string;
|
|
516
|
+
suggestion: string;
|
|
517
|
+
targetSlug: string;
|
|
518
|
+
createdLabel: string;
|
|
519
|
+
no404Errors: string;
|
|
520
|
+
pages404Desc: string;
|
|
521
|
+
last: string;
|
|
522
|
+
ref: string;
|
|
523
|
+
ignore: string;
|
|
524
|
+
checkExternalLinksDesc: string;
|
|
525
|
+
scanExternalLinks: string;
|
|
526
|
+
verificationInProgress: string;
|
|
527
|
+
noBrokenLinks: string;
|
|
528
|
+
noExternalLinks: string;
|
|
529
|
+
total: string;
|
|
530
|
+
broken: string;
|
|
531
|
+
timeout: string;
|
|
532
|
+
all: string;
|
|
533
|
+
brokenLabel: string;
|
|
534
|
+
rescan: string;
|
|
535
|
+
forceNewVerification: string;
|
|
536
|
+
analyzingInternal: string;
|
|
537
|
+
brokenLinkTo: string;
|
|
538
|
+
linkFrom: string;
|
|
539
|
+
};
|
|
540
|
+
seoConfig: {
|
|
541
|
+
loading: string;
|
|
542
|
+
title: string;
|
|
543
|
+
subtitle: string;
|
|
544
|
+
saved: string;
|
|
545
|
+
siteName: string;
|
|
546
|
+
siteNameDesc: string;
|
|
547
|
+
ignoredPages: string;
|
|
548
|
+
ignoredPagesDesc: string;
|
|
549
|
+
pageSlugPlaceholder: string;
|
|
550
|
+
noIgnoredSlugs: string;
|
|
551
|
+
disabledRules: string;
|
|
552
|
+
disabledRulesDesc: string;
|
|
553
|
+
customThresholds: string;
|
|
554
|
+
customThresholdsDesc: string;
|
|
555
|
+
defaultLabel: string;
|
|
556
|
+
ruleGroupTitle: string;
|
|
557
|
+
ruleGroupMetaDescription: string;
|
|
558
|
+
ruleGroupUrlSlug: string;
|
|
559
|
+
ruleGroupHeadings: string;
|
|
560
|
+
ruleGroupContent: string;
|
|
561
|
+
ruleGroupImages: string;
|
|
562
|
+
ruleGroupLinks: string;
|
|
563
|
+
ruleGroupSocial: string;
|
|
564
|
+
ruleGroupStructuredData: string;
|
|
565
|
+
ruleGroupReadability: string;
|
|
566
|
+
ruleGroupQuality: string;
|
|
567
|
+
ruleGroupSecondaryKeywords: string;
|
|
568
|
+
ruleGroupCornerstone: string;
|
|
569
|
+
ruleGroupFreshness: string;
|
|
570
|
+
ruleGroupTechnical: string;
|
|
571
|
+
ruleGroupAccessibility: string;
|
|
572
|
+
ruleGroupEcommerce: string;
|
|
573
|
+
thresholdTitleMin: string;
|
|
574
|
+
thresholdTitleMax: string;
|
|
575
|
+
thresholdMetaDescMin: string;
|
|
576
|
+
thresholdMetaDescMax: string;
|
|
577
|
+
thresholdMinWordsPages: string;
|
|
578
|
+
thresholdMinWordsPosts: string;
|
|
579
|
+
thresholdKeywordDensityMin: string;
|
|
580
|
+
thresholdKeywordDensityMax: string;
|
|
581
|
+
thresholdFleschMin: string;
|
|
582
|
+
thresholdSlugMaxLength: string;
|
|
583
|
+
sitemapConfig: string;
|
|
584
|
+
sitemapExcludedSlugs: string;
|
|
585
|
+
slugToExcludePlaceholder: string;
|
|
586
|
+
noExcludedSlugs: string;
|
|
587
|
+
defaultChangeFrequency: string;
|
|
588
|
+
daily: string;
|
|
589
|
+
weekly: string;
|
|
590
|
+
monthly: string;
|
|
591
|
+
yearly: string;
|
|
592
|
+
defaultPriority: string;
|
|
593
|
+
defaultPriorityDesc: string;
|
|
594
|
+
priorityOverrides: string;
|
|
595
|
+
priorityOverridesDesc: string;
|
|
596
|
+
patternPlaceholder: string;
|
|
597
|
+
priority: string;
|
|
598
|
+
defaultShort: string;
|
|
599
|
+
addOverride: string;
|
|
600
|
+
sitemapPreview: string;
|
|
601
|
+
viewPreview: string;
|
|
602
|
+
refreshPreview: string;
|
|
603
|
+
pages: string;
|
|
604
|
+
included: string;
|
|
605
|
+
excluded: string;
|
|
606
|
+
url: string;
|
|
607
|
+
lastModified: string;
|
|
608
|
+
frequency: string;
|
|
609
|
+
breadcrumbConfig: string;
|
|
610
|
+
breadcrumbConfigDesc: string;
|
|
611
|
+
enableBreadcrumbs: string;
|
|
612
|
+
homePageLabel: string;
|
|
613
|
+
separator: string;
|
|
614
|
+
separatorDesc: string;
|
|
615
|
+
showOnHomePage: string;
|
|
616
|
+
showOnHomePageDesc: string;
|
|
617
|
+
preview: string;
|
|
618
|
+
websiteCreation: string;
|
|
619
|
+
};
|
|
620
|
+
redirectManager: {
|
|
621
|
+
loading: string;
|
|
622
|
+
title: string;
|
|
623
|
+
totalRedirects: string;
|
|
624
|
+
importCsv: string;
|
|
625
|
+
total: string;
|
|
626
|
+
permanent301: string;
|
|
627
|
+
temporary302: string;
|
|
628
|
+
addRedirect: string;
|
|
629
|
+
sourceUrl: string;
|
|
630
|
+
sourceUrlPlaceholder: string;
|
|
631
|
+
destinationUrl: string;
|
|
632
|
+
destinationUrlPlaceholder: string;
|
|
633
|
+
type: string;
|
|
634
|
+
adding: string;
|
|
635
|
+
searchPlaceholder: string;
|
|
636
|
+
urlToTestPlaceholder: string;
|
|
637
|
+
test: string;
|
|
638
|
+
noMatch: string;
|
|
639
|
+
sourceFrom: string;
|
|
640
|
+
destinationTo: string;
|
|
641
|
+
date: string;
|
|
642
|
+
actions: string;
|
|
643
|
+
noMatchingRedirects: string;
|
|
644
|
+
noRedirects: string;
|
|
645
|
+
redirectCreated: string;
|
|
646
|
+
redirectDeleted: string;
|
|
647
|
+
redirectsDeleted: string;
|
|
648
|
+
redirectModified: string;
|
|
649
|
+
noValidCsvRedirects: string;
|
|
650
|
+
redirectsExported: string;
|
|
651
|
+
exportError: string;
|
|
652
|
+
createdCount: string;
|
|
653
|
+
duplicatesCount: string;
|
|
654
|
+
errorsCount: string;
|
|
655
|
+
};
|
|
656
|
+
performance: {
|
|
657
|
+
days7: string;
|
|
658
|
+
days30: string;
|
|
659
|
+
days90: string;
|
|
660
|
+
loading: string;
|
|
661
|
+
title: string;
|
|
662
|
+
subtitle: string;
|
|
663
|
+
closeImport: string;
|
|
664
|
+
import: string;
|
|
665
|
+
importGscData: string;
|
|
666
|
+
fileType: string;
|
|
667
|
+
pasteJsonHint: string;
|
|
668
|
+
importing: string;
|
|
669
|
+
importedCount: string;
|
|
670
|
+
updatedCount: string;
|
|
671
|
+
noData: string;
|
|
672
|
+
noDataDesc: string;
|
|
673
|
+
importData: string;
|
|
674
|
+
totalClicks: string;
|
|
675
|
+
totalImpressions: string;
|
|
676
|
+
averageCtr: string;
|
|
677
|
+
averagePosition: string;
|
|
678
|
+
topPages: string;
|
|
679
|
+
clicks: string;
|
|
680
|
+
impressions: string;
|
|
681
|
+
ctrPercent: string;
|
|
682
|
+
position: string;
|
|
683
|
+
topQueries: string;
|
|
684
|
+
query: string;
|
|
685
|
+
xlsxEntriesLoaded: string;
|
|
686
|
+
pages: string;
|
|
687
|
+
queries: string;
|
|
688
|
+
};
|
|
689
|
+
schemaBuilder: {
|
|
690
|
+
localBusiness: string;
|
|
691
|
+
article: string;
|
|
692
|
+
product: string;
|
|
693
|
+
faq: string;
|
|
694
|
+
howTo: string;
|
|
695
|
+
organization: string;
|
|
696
|
+
event: string;
|
|
697
|
+
choose: string;
|
|
698
|
+
jsonLdPreview: string;
|
|
699
|
+
liveUpdate: string;
|
|
700
|
+
copyJsonLd: string;
|
|
701
|
+
copyScriptTag: string;
|
|
702
|
+
copied: string;
|
|
703
|
+
copyError: string;
|
|
704
|
+
tip: string;
|
|
705
|
+
beforeDeploying: string;
|
|
706
|
+
visualGeneratorDesc: string;
|
|
707
|
+
addButton: string;
|
|
708
|
+
name: string;
|
|
709
|
+
description: string;
|
|
710
|
+
address: string;
|
|
711
|
+
city: string;
|
|
712
|
+
postalCode: string;
|
|
713
|
+
country: string;
|
|
714
|
+
phone: string;
|
|
715
|
+
email: string;
|
|
716
|
+
openingHours: string;
|
|
717
|
+
latitude: string;
|
|
718
|
+
longitude: string;
|
|
719
|
+
website: string;
|
|
720
|
+
contactEmail: string;
|
|
721
|
+
socialMediaUrls: string;
|
|
722
|
+
startDate: string;
|
|
723
|
+
endDate: string;
|
|
724
|
+
location: string;
|
|
725
|
+
locationAddress: string;
|
|
726
|
+
ticketUrl: string;
|
|
727
|
+
title: string;
|
|
728
|
+
author: string;
|
|
729
|
+
publisher: string;
|
|
730
|
+
imageUrl: string;
|
|
731
|
+
publicationDate: string;
|
|
732
|
+
sku: string;
|
|
733
|
+
brand: string;
|
|
734
|
+
price: string;
|
|
735
|
+
currency: string;
|
|
736
|
+
availability: string;
|
|
737
|
+
inStock: string;
|
|
738
|
+
outOfStock: string;
|
|
739
|
+
preOrder: string;
|
|
740
|
+
madeToOrder: string;
|
|
741
|
+
questionsAnswers: string;
|
|
742
|
+
question: string;
|
|
743
|
+
answer: string;
|
|
744
|
+
steps: string;
|
|
745
|
+
stepTitle: string;
|
|
746
|
+
stepDescription: string;
|
|
747
|
+
priceRange: string;
|
|
748
|
+
modificationDate: string;
|
|
749
|
+
publisherName: string;
|
|
750
|
+
publisherLogo: string;
|
|
751
|
+
productName: string;
|
|
752
|
+
reviewCount: string;
|
|
753
|
+
ratingValue: string;
|
|
754
|
+
guideTitle: string;
|
|
755
|
+
logoUrl: string;
|
|
756
|
+
imageLabel: string;
|
|
757
|
+
urlLabel: string;
|
|
758
|
+
validateOnRichResults: string;
|
|
759
|
+
};
|
|
760
|
+
keywordResearch: {
|
|
761
|
+
loading: string;
|
|
762
|
+
title: string;
|
|
763
|
+
subtitle: string;
|
|
764
|
+
activeKeywords: string;
|
|
765
|
+
uniqueTerms: string;
|
|
766
|
+
suggestions: string;
|
|
767
|
+
searchPlaceholder: string;
|
|
768
|
+
noSuggestions: string;
|
|
769
|
+
noSuggestionsDesc: string;
|
|
770
|
+
noMatchingSuggestions: string;
|
|
771
|
+
unused: string;
|
|
772
|
+
associated: string;
|
|
773
|
+
trending: string;
|
|
774
|
+
longTail: string;
|
|
775
|
+
all: string;
|
|
776
|
+
unusedPlural: string;
|
|
777
|
+
associatedPlural: string;
|
|
778
|
+
trendingPlural: string;
|
|
779
|
+
keyword: string;
|
|
780
|
+
freq: string;
|
|
781
|
+
seeLess: string;
|
|
782
|
+
type: string;
|
|
783
|
+
score: string;
|
|
784
|
+
frequency: string;
|
|
785
|
+
usedBy: string;
|
|
786
|
+
suggestedFor: string;
|
|
787
|
+
};
|
|
788
|
+
cannibalization: {
|
|
789
|
+
loading: string;
|
|
790
|
+
title: string;
|
|
791
|
+
subtitle: string;
|
|
792
|
+
conflicts: string;
|
|
793
|
+
affectedPages: string;
|
|
794
|
+
searchPlaceholder: string;
|
|
795
|
+
noCannibalization: string;
|
|
796
|
+
noCannibalizationDesc: string;
|
|
797
|
+
noMatchingConflicts: string;
|
|
798
|
+
highRisk: string;
|
|
799
|
+
warning: string;
|
|
800
|
+
pages: string;
|
|
801
|
+
};
|
|
802
|
+
seoAnalyzer: {
|
|
803
|
+
groupTitle: string;
|
|
804
|
+
groupDescription: string;
|
|
805
|
+
groupUrlSlug: string;
|
|
806
|
+
groupHeadings: string;
|
|
807
|
+
groupContent: string;
|
|
808
|
+
groupImages: string;
|
|
809
|
+
groupLinks: string;
|
|
810
|
+
groupSocial: string;
|
|
811
|
+
groupStructuredData: string;
|
|
812
|
+
groupReadability: string;
|
|
813
|
+
groupQuality: string;
|
|
814
|
+
groupSecondaryKeywords: string;
|
|
815
|
+
groupCornerstone: string;
|
|
816
|
+
groupFreshness: string;
|
|
817
|
+
groupTechnical: string;
|
|
818
|
+
groupAccessibility: string;
|
|
819
|
+
groupEcommerce: string;
|
|
820
|
+
levelExcellent: string;
|
|
821
|
+
levelGood: string;
|
|
822
|
+
levelFair: string;
|
|
823
|
+
levelNeedsImprovement: string;
|
|
824
|
+
categoryCritical: string;
|
|
825
|
+
categoryImportant: string;
|
|
826
|
+
categoryBonus: string;
|
|
827
|
+
seoScore: string;
|
|
828
|
+
outOf100: string;
|
|
829
|
+
cornerstoneLabel: string;
|
|
830
|
+
checksPassed: string;
|
|
831
|
+
errorsCount: string;
|
|
832
|
+
warningsCount: string;
|
|
833
|
+
improvementSuggestions: string;
|
|
834
|
+
adjustTitle: string;
|
|
835
|
+
currently: string;
|
|
836
|
+
titleCharactersIdeal: string;
|
|
837
|
+
writeMetaDesc: string;
|
|
838
|
+
enrichContent: string;
|
|
839
|
+
includeKeywordInTitle: string;
|
|
840
|
+
includeKeywordInDesc: string;
|
|
841
|
+
addAltText: string;
|
|
842
|
+
addInternalLinks: string;
|
|
843
|
+
seoCannibalization: string;
|
|
844
|
+
highRisk: string;
|
|
845
|
+
duplicationHarmsRanking: string;
|
|
846
|
+
diluteRanking: string;
|
|
847
|
+
viewPages: string;
|
|
848
|
+
uniqueKeywordAdvice: string;
|
|
849
|
+
suggestedInternalLinks: string;
|
|
850
|
+
analyzingContent: string;
|
|
851
|
+
readingTime: string;
|
|
852
|
+
wordsLabel: string;
|
|
853
|
+
secondaryKeywords: string;
|
|
854
|
+
generateMeta: string;
|
|
855
|
+
metaTitle: string;
|
|
856
|
+
metaDescription: string;
|
|
857
|
+
emptyValue: string;
|
|
858
|
+
};
|
|
859
|
+
scoreHistory: {
|
|
860
|
+
loading: string;
|
|
861
|
+
loadingError: string;
|
|
862
|
+
noHistory: string;
|
|
863
|
+
noHistoryDesc: string;
|
|
864
|
+
improving: string;
|
|
865
|
+
declining: string;
|
|
866
|
+
stable: string;
|
|
867
|
+
scoreEvolution: string;
|
|
868
|
+
measures: string;
|
|
869
|
+
latestScore: string;
|
|
870
|
+
};
|
|
871
|
+
contentDecay: {
|
|
872
|
+
title: string;
|
|
873
|
+
lessThan3Months: string;
|
|
874
|
+
months3to6: string;
|
|
875
|
+
months6to12: string;
|
|
876
|
+
moreThan12Months: string;
|
|
877
|
+
description: string;
|
|
878
|
+
lastUpdate: string;
|
|
879
|
+
lastReview: string;
|
|
880
|
+
ageDays: string;
|
|
881
|
+
action: string;
|
|
882
|
+
reviewed: string;
|
|
883
|
+
markReviewed: string;
|
|
884
|
+
noData: string;
|
|
885
|
+
};
|
|
886
|
+
socialPreview: {
|
|
887
|
+
title: string;
|
|
888
|
+
facebook: string;
|
|
889
|
+
twitter: string;
|
|
890
|
+
ogImage: string;
|
|
891
|
+
cardImage: string;
|
|
892
|
+
previewTitle: string;
|
|
893
|
+
previewDescription: string;
|
|
894
|
+
characters: string;
|
|
895
|
+
pageTitlePlaceholder: string;
|
|
896
|
+
pageDescriptionPlaceholder: string;
|
|
897
|
+
};
|
|
898
|
+
serpPreview: {
|
|
899
|
+
title: string;
|
|
900
|
+
desktop: string;
|
|
901
|
+
mobile: string;
|
|
902
|
+
noMetaTitle: string;
|
|
903
|
+
noMetaDescription: string;
|
|
904
|
+
previewTitle: string;
|
|
905
|
+
previewDescription: string;
|
|
906
|
+
url: string;
|
|
907
|
+
characters: string;
|
|
908
|
+
};
|
|
909
|
+
metaTitle: {
|
|
910
|
+
label: string;
|
|
911
|
+
placeholder: string;
|
|
912
|
+
characters: string;
|
|
913
|
+
optimal: string;
|
|
914
|
+
charactersMissing: string;
|
|
915
|
+
charactersTooMany: string;
|
|
916
|
+
idealLength: string;
|
|
917
|
+
};
|
|
918
|
+
metaDescription: {
|
|
919
|
+
label: string;
|
|
920
|
+
placeholder: string;
|
|
921
|
+
characters: string;
|
|
922
|
+
optimal: string;
|
|
923
|
+
charactersMissing: string;
|
|
924
|
+
charactersTooMany: string;
|
|
925
|
+
idealLength: string;
|
|
926
|
+
};
|
|
927
|
+
metaImage: {
|
|
928
|
+
label: string;
|
|
929
|
+
imageSet: string;
|
|
930
|
+
noImage: string;
|
|
931
|
+
set: string;
|
|
932
|
+
};
|
|
933
|
+
overview: {
|
|
934
|
+
metaCompleteness: string;
|
|
935
|
+
incomplete: string;
|
|
936
|
+
partial: string;
|
|
937
|
+
almostComplete: string;
|
|
938
|
+
complete: string;
|
|
939
|
+
titleLabel: string;
|
|
940
|
+
descriptionLabel: string;
|
|
941
|
+
imageLabel: string;
|
|
942
|
+
set: string;
|
|
943
|
+
missing: string;
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Register custom translations for a locale.
|
|
948
|
+
* Supports partial overrides — only the keys you provide will be replaced.
|
|
949
|
+
* Call this before the dashboard renders (e.g. in plugin init).
|
|
950
|
+
*
|
|
951
|
+
* @example
|
|
952
|
+
* ```ts
|
|
953
|
+
* registerDashboardTranslations('cs', {
|
|
954
|
+
* common: { loading: 'Načítání...', save: 'Uložit', cancel: 'Zrušit', ... },
|
|
955
|
+
* nav: { dashboard: 'Přehled', seo: 'SEO', ... },
|
|
956
|
+
* ...
|
|
957
|
+
* })
|
|
958
|
+
* ```
|
|
959
|
+
*/
|
|
960
|
+
declare function registerDashboardTranslations(locale: string, custom: Partial<DashboardTranslations> | DashboardTranslations,
|
|
961
|
+
/** Base locale to use for missing keys (default: 'en') */
|
|
962
|
+
fallbackLocale?: string): void;
|
|
963
|
+
declare function getDashboardT(locale: DashboardLocale): DashboardTranslations;
|
|
964
|
+
|
|
965
|
+
/** Arguments passed to generate functions (generateTitle, generateDescription, etc.) */
|
|
966
|
+
interface GenerateFnArgs$1 {
|
|
967
|
+
doc: Record<string, unknown>;
|
|
968
|
+
locale?: string;
|
|
969
|
+
req: unknown;
|
|
970
|
+
collectionSlug?: string;
|
|
971
|
+
globalSlug?: string;
|
|
972
|
+
}
|
|
973
|
+
interface SeoPluginConfig {
|
|
974
|
+
/** Collections to add SEO fields to (default: ['pages', 'posts']) */
|
|
975
|
+
collections?: string[];
|
|
976
|
+
/** Globals to add SEO fields to (default: []) */
|
|
977
|
+
globals?: string[];
|
|
978
|
+
/** Whether to add the SEO dashboard view at /admin/seo (default: true) */
|
|
979
|
+
addDashboardView?: boolean;
|
|
980
|
+
/** Rule groups to disable entirely */
|
|
981
|
+
disabledRules?: RuleGroup[];
|
|
982
|
+
/** Override the weight of all checks within a rule group */
|
|
983
|
+
overrideWeights?: Partial<Record<RuleGroup, number>>;
|
|
984
|
+
/** Custom thresholds (override defaults from constants) */
|
|
985
|
+
thresholds?: SeoThresholds;
|
|
986
|
+
/** Additional local SEO slugs for the project */
|
|
987
|
+
localSeoSlugs?: string[];
|
|
988
|
+
/** Site name (used for brand duplicate check in titles) */
|
|
989
|
+
siteName?: string;
|
|
990
|
+
/** Base URL of the site (used for canonical URL validation, e.g. 'https://example.com') */
|
|
991
|
+
siteUrl?: string;
|
|
992
|
+
/** Base path for API endpoints (default: '/seo-plugin') */
|
|
993
|
+
endpointBasePath?: string;
|
|
994
|
+
/** Whether to track SEO score history (adds a collection + afterChange hook, default: true) */
|
|
995
|
+
trackScoreHistory?: boolean;
|
|
996
|
+
/** Whether to add the sitemap audit view at /admin/sitemap-audit (default: true) */
|
|
997
|
+
addSitemapAuditView?: boolean;
|
|
998
|
+
/** Collection slug for redirects (default: 'seo-redirects'). The plugin auto-creates this collection. */
|
|
999
|
+
redirectsCollection?: string;
|
|
1000
|
+
/** Known dynamic routes that are not stored as document slugs (e.g. ['blog', 'réalisations', 'posts']). These won't be flagged as broken links or orphan pages. */
|
|
1001
|
+
knownRoutes?: string[];
|
|
1002
|
+
/** Secret header value for seo-logs POST endpoint. If set, POST requests must include X-SEO-Secret header with this value. If not set, POST requires authenticated admin user. */
|
|
1003
|
+
seoLogsSecret?: string;
|
|
1004
|
+
/** Locale for language-specific analysis (default: 'fr') */
|
|
1005
|
+
locale?: 'fr' | 'en';
|
|
1006
|
+
/** Collection slug for uploads/media (used for meta.image relationTo). Default: 'media' */
|
|
1007
|
+
uploadsCollection?: string;
|
|
1008
|
+
/** Auto-create meta fields (title, description, image) on target collections. Default: true */
|
|
1009
|
+
autoCreateMetaFields?: boolean;
|
|
1010
|
+
/** Granular feature flags — all default to true. Disable features you don't need
|
|
1011
|
+
* to reduce collections, endpoints, and admin views loaded by the plugin.
|
|
1012
|
+
* The core analyzer sidebar, validate endpoint, and meta fields are always active. */
|
|
1013
|
+
features?: SeoFeatures;
|
|
1014
|
+
/** Custom function to generate meta title */
|
|
1015
|
+
generateTitle?: (args: GenerateFnArgs$1) => string | Promise<string>;
|
|
1016
|
+
/** Custom function to generate meta description */
|
|
1017
|
+
generateDescription?: (args: GenerateFnArgs$1) => string | Promise<string>;
|
|
1018
|
+
/** Custom function to generate meta image (returns media ID or URL) */
|
|
1019
|
+
generateImage?: (args: GenerateFnArgs$1) => string | number | Promise<string | number>;
|
|
1020
|
+
/** Custom function to generate page URL */
|
|
1021
|
+
generateURL?: (args: GenerateFnArgs$1) => string | Promise<string>;
|
|
1022
|
+
/** Mapping from Payload locale codes to analysis locale ('fr' | 'en') */
|
|
1023
|
+
localeMapping?: Record<string, 'fr' | 'en'>;
|
|
1024
|
+
/** Custom dashboard translations for additional locales (e.g. 'cs', 'de', 'es').
|
|
1025
|
+
* Partial overrides are supported — missing keys fall back to English.
|
|
1026
|
+
* @example
|
|
1027
|
+
* ```ts
|
|
1028
|
+
* customTranslations: {
|
|
1029
|
+
* cs: {
|
|
1030
|
+
* common: { loading: 'Načítání...', save: 'Uložit' },
|
|
1031
|
+
* nav: { dashboard: 'Přehled', seo: 'SEO' },
|
|
1032
|
+
* }
|
|
1033
|
+
* }
|
|
1034
|
+
* ```
|
|
1035
|
+
*/
|
|
1036
|
+
customTranslations?: Record<string, Partial<DashboardTranslations>>;
|
|
1037
|
+
/** Override or reorganize the default meta fields inside the 'meta' group.
|
|
1038
|
+
* Receives the default fields (overview, title, description, image, preview) and must return a Field[].
|
|
1039
|
+
* Use this to add custom fields, remove defaults, or reorder them. */
|
|
1040
|
+
fields?: (args: {
|
|
1041
|
+
defaultFields: Field[];
|
|
1042
|
+
}) => Field[];
|
|
1043
|
+
/** If true, wraps collection/global fields in a tabbed UI with "Content" and "SEO" tabs.
|
|
1044
|
+
* Compatible with collections that already use a tabs field as their first field. */
|
|
1045
|
+
tabbedUI?: boolean;
|
|
1046
|
+
/** Custom TypeScript interface name for the generated meta group type (e.g. 'SharedSEO') */
|
|
1047
|
+
interfaceName?: string;
|
|
1048
|
+
}
|
|
1049
|
+
declare const seoAnalyzerPlugin: (pluginConfig?: SeoPluginConfig) => Plugin;
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* SEO fields added automatically to collections by the plugin.
|
|
1053
|
+
* Includes: focusKeyword, focusKeywords array, isCornerstone, and the SeoAnalyzer UI.
|
|
1054
|
+
*/
|
|
1055
|
+
|
|
1056
|
+
declare function seoFields(): Field[];
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Auto-created meta fields definition.
|
|
1060
|
+
* Generates a "meta" group field with title, description, image, overview, and preview sub-fields.
|
|
1061
|
+
* These fields mirror @payloadcms/plugin-seo's field structure for compatibility.
|
|
1062
|
+
*
|
|
1063
|
+
* Usage:
|
|
1064
|
+
* import { metaFields } from '@consilioweb/payload-seo-analyzer'
|
|
1065
|
+
* // ...fields: [...metaFields({ uploadsCollection: 'media', hasGenerateTitle: true })]
|
|
1066
|
+
*/
|
|
1067
|
+
|
|
1068
|
+
interface MetaFieldsConfig {
|
|
1069
|
+
/** Collection slug for image uploads (default: 'media') */
|
|
1070
|
+
uploadsCollection?: string;
|
|
1071
|
+
/** Custom TypeScript interface name for the meta group (for generated types) */
|
|
1072
|
+
interfaceName?: string;
|
|
1073
|
+
/** Whether a generateTitle function is configured */
|
|
1074
|
+
hasGenerateTitle?: boolean;
|
|
1075
|
+
/** Whether a generateDescription function is configured */
|
|
1076
|
+
hasGenerateDescription?: boolean;
|
|
1077
|
+
/** Whether a generateImage function is configured */
|
|
1078
|
+
hasGenerateImage?: boolean;
|
|
1079
|
+
/** Base API path for the generate endpoint (default: '/api/seo-plugin') */
|
|
1080
|
+
basePath?: string;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Generate the meta field group (title, description, image, overview, preview).
|
|
1084
|
+
* Compatible with @payloadcms/plugin-seo's field naming for easy migration.
|
|
1085
|
+
*/
|
|
1086
|
+
declare function metaFields(config?: MetaFieldsConfig): Field[];
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* SEO Plugin — Lightweight i18n translation system.
|
|
1090
|
+
* Provides French (default) and English translations for all plugin views
|
|
1091
|
+
* and all rule messages/labels/tips.
|
|
1092
|
+
* No external dependencies.
|
|
1093
|
+
*/
|
|
1094
|
+
type SeoLocale = 'fr' | 'en';
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Resolve the effective analysis locale from multiple sources.
|
|
1098
|
+
*
|
|
1099
|
+
* Cascade order:
|
|
1100
|
+
* 1. Explicit pluginLocale override (from SeoPluginConfig.locale)
|
|
1101
|
+
* 2. Custom mapping (pluginConfig.localeMapping) applied to reqLocale
|
|
1102
|
+
* 3. Auto-detect from reqLocale (req.locale from Payload's i18n)
|
|
1103
|
+
* 4. Fallback → 'fr'
|
|
1104
|
+
*
|
|
1105
|
+
* Mapping logic: 'fr*' → 'fr', 'en*' → 'en', anything else → fallback
|
|
1106
|
+
*/
|
|
1107
|
+
|
|
1108
|
+
interface ResolveLocaleArgs {
|
|
1109
|
+
/** req.locale from Payload (e.g. 'fr', 'en', 'en-US', 'fr-FR') */
|
|
1110
|
+
reqLocale?: string;
|
|
1111
|
+
/** Static locale from SeoPluginConfig.locale — overrides everything */
|
|
1112
|
+
pluginLocale?: 'fr' | 'en';
|
|
1113
|
+
/** Custom mapping from Payload locale → analysis locale */
|
|
1114
|
+
customMapping?: Record<string, 'fr' | 'en'>;
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Resolve the effective locale for SEO analysis.
|
|
1118
|
+
* Returns 'fr' or 'en'.
|
|
1119
|
+
*/
|
|
1120
|
+
declare function resolveAnalysisLocale(args: ResolveLocaleArgs): SeoLocale;
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Fetch all documents from target collections + globals in a uniform format.
|
|
1124
|
+
* Reused by audit, cannibalization, linkGraph, keywordResearch, etc.
|
|
1125
|
+
*/
|
|
1126
|
+
|
|
1127
|
+
type DocSourceType = 'collection' | 'global';
|
|
1128
|
+
interface FetchedDoc {
|
|
1129
|
+
doc: any;
|
|
1130
|
+
sourceType: DocSourceType;
|
|
1131
|
+
sourceSlug: string;
|
|
1132
|
+
}
|
|
1133
|
+
interface FetchAllDocsOptions {
|
|
1134
|
+
collections: string[];
|
|
1135
|
+
globals?: string[];
|
|
1136
|
+
/** Limit per collection query (default: 500) */
|
|
1137
|
+
limit?: number;
|
|
1138
|
+
/** Depth for population (default: 1) */
|
|
1139
|
+
depth?: number;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Fetch all documents from the specified collections and globals.
|
|
1143
|
+
* Returns a unified array of { doc, sourceType, sourceSlug }.
|
|
1144
|
+
*/
|
|
1145
|
+
declare function fetchAllDocs(payload: Payload, options: FetchAllDocsOptions): Promise<FetchedDoc[]>;
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* SEO Validate endpoint handler.
|
|
1149
|
+
* Runs the full SEO engine (50+ checks) on a page or post.
|
|
1150
|
+
*
|
|
1151
|
+
* NOTE: Rate limiting is not handled by this plugin. The consuming application
|
|
1152
|
+
* should implement rate limiting via its own middleware (e.g., express-rate-limit,
|
|
1153
|
+
* Next.js middleware, or a reverse proxy like Nginx/Caddy).
|
|
1154
|
+
*/
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Build a SeoInput object from a Payload document (page or post).
|
|
1158
|
+
* Exported for reuse in other endpoints or custom integrations.
|
|
1159
|
+
*/
|
|
1160
|
+
declare function buildSeoInputFromDoc(doc: any, collection: string, options?: {
|
|
1161
|
+
isGlobal?: boolean;
|
|
1162
|
+
}): SeoInput;
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* SEO Score History endpoint handler.
|
|
1166
|
+
* Returns score history + trend for a specific document.
|
|
1167
|
+
*
|
|
1168
|
+
* Query params:
|
|
1169
|
+
* - documentId (required) — ID of the document
|
|
1170
|
+
* - collection (required) — collection slug ('pages', 'posts')
|
|
1171
|
+
* - limit (optional, default: 30) — max snapshots to return
|
|
1172
|
+
*
|
|
1173
|
+
* Response:
|
|
1174
|
+
* { history: [...], trend: 'improving' | 'declining' | 'stable', scoreDelta: number }
|
|
1175
|
+
*
|
|
1176
|
+
* NOTE: Rate limiting is not handled by this plugin. The consuming application
|
|
1177
|
+
* should implement rate limiting via its own middleware (e.g., express-rate-limit,
|
|
1178
|
+
* Next.js middleware, or a reverse proxy like Nginx/Caddy).
|
|
1179
|
+
*/
|
|
1180
|
+
|
|
1181
|
+
declare function createHistoryHandler(): PayloadHandler;
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Sitemap Audit endpoint handler.
|
|
1185
|
+
* Performs a full internal link graph analysis to detect orphan pages,
|
|
1186
|
+
* weak pages, link hubs, and broken internal links.
|
|
1187
|
+
*
|
|
1188
|
+
* NOTE: Rate limiting is not handled by this plugin. The consuming application
|
|
1189
|
+
* should implement rate limiting via its own middleware (e.g., express-rate-limit,
|
|
1190
|
+
* Next.js middleware, or a reverse proxy like Nginx/Caddy).
|
|
1191
|
+
*/
|
|
1192
|
+
|
|
1193
|
+
declare function createSitemapAuditHandler(collections: string[], redirectsCollection?: string, knownRoutes?: string[]): PayloadHandler;
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* SEO Score History collection.
|
|
1197
|
+
* Stores SEO score snapshots over time for trend tracking.
|
|
1198
|
+
*
|
|
1199
|
+
* Usage (in plugin.ts):
|
|
1200
|
+
* config.collections = [
|
|
1201
|
+
* ...(config.collections || []),
|
|
1202
|
+
* createSeoScoreHistoryCollection(),
|
|
1203
|
+
* ]
|
|
1204
|
+
*/
|
|
1205
|
+
|
|
1206
|
+
declare function createSeoScoreHistoryCollection(): CollectionConfig;
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* SEO Performance collection.
|
|
1210
|
+
* Stores Google Search Console performance data imported via CSV or API.
|
|
1211
|
+
* Each row represents one URL+query combination for a given date.
|
|
1212
|
+
*
|
|
1213
|
+
* Usage (in plugin.ts):
|
|
1214
|
+
* config.collections = [
|
|
1215
|
+
* ...(config.collections || []),
|
|
1216
|
+
* createSeoPerformanceCollection(),
|
|
1217
|
+
* ]
|
|
1218
|
+
*/
|
|
1219
|
+
|
|
1220
|
+
declare function createSeoPerformanceCollection(): CollectionConfig;
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* SEO Score tracking hook.
|
|
1224
|
+
* Automatically records a score snapshot after each document save.
|
|
1225
|
+
*
|
|
1226
|
+
* Usage (in plugin.ts):
|
|
1227
|
+
* collection.hooks.afterChange = [
|
|
1228
|
+
* ...(collection.hooks?.afterChange || []),
|
|
1229
|
+
* createTrackSeoScoreHook(seoConfig),
|
|
1230
|
+
* ]
|
|
1231
|
+
*/
|
|
1232
|
+
|
|
1233
|
+
declare function createTrackSeoScoreHook(seoConfig?: SeoConfig): CollectionAfterChangeHook;
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Performance data endpoint.
|
|
1237
|
+
* GET — Fetch performance data with aggregation by period.
|
|
1238
|
+
* POST — Import performance data from CSV string or JSON entries array.
|
|
1239
|
+
*
|
|
1240
|
+
* NOTE: Rate limiting is not handled by this plugin. The consuming application
|
|
1241
|
+
* should implement rate limiting via its own middleware (e.g., express-rate-limit,
|
|
1242
|
+
* Next.js middleware, or a reverse proxy like Nginx/Caddy).
|
|
1243
|
+
*/
|
|
1244
|
+
|
|
1245
|
+
declare function createPerformanceHandler(): PayloadHandler;
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* Keyword Research endpoint.
|
|
1249
|
+
* Analyzes existing content across target collections to suggest keywords.
|
|
1250
|
+
* Uses TF-IDF scoring, stop word filtering, and focusKeyword gap analysis.
|
|
1251
|
+
*
|
|
1252
|
+
* NOTE: Rate limiting is not handled by this plugin. The consuming application
|
|
1253
|
+
* should implement rate limiting via its own middleware (e.g., express-rate-limit,
|
|
1254
|
+
* Next.js middleware, or a reverse proxy like Nginx/Caddy).
|
|
1255
|
+
*/
|
|
1256
|
+
|
|
1257
|
+
declare function createKeywordResearchHandler(targetCollections: string[], globals?: string[]): PayloadHandler;
|
|
1258
|
+
|
|
1259
|
+
/**
|
|
1260
|
+
* SEO Generate endpoint handler.
|
|
1261
|
+
* Calls user-provided generate functions (generateTitle, generateDescription,
|
|
1262
|
+
* generateImage, generateURL) to produce meta values from a Payload document.
|
|
1263
|
+
*
|
|
1264
|
+
* The consuming application registers generate functions via the plugin config,
|
|
1265
|
+
* and this endpoint exposes them as a POST API for the admin UI or external tools.
|
|
1266
|
+
*
|
|
1267
|
+
* NOTE: Rate limiting is not handled by this plugin. The consuming application
|
|
1268
|
+
* should implement rate limiting via its own middleware (e.g., express-rate-limit,
|
|
1269
|
+
* Next.js middleware, or a reverse proxy like Nginx/Caddy).
|
|
1270
|
+
*/
|
|
1271
|
+
|
|
1272
|
+
interface GenerateFnArgs {
|
|
1273
|
+
doc: Record<string, unknown>;
|
|
1274
|
+
locale?: string;
|
|
1275
|
+
req: unknown;
|
|
1276
|
+
collectionSlug?: string;
|
|
1277
|
+
globalSlug?: string;
|
|
1278
|
+
}
|
|
1279
|
+
interface GeneratePluginConfig {
|
|
1280
|
+
generateTitle?: (args: GenerateFnArgs) => string | Promise<string>;
|
|
1281
|
+
generateDescription?: (args: GenerateFnArgs) => string | Promise<string>;
|
|
1282
|
+
generateImage?: (args: GenerateFnArgs) => string | number | Promise<string | number>;
|
|
1283
|
+
generateURL?: (args: GenerateFnArgs) => string | Promise<string>;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Creates a Payload endpoint handler that invokes user-provided generate
|
|
1287
|
+
* functions (title, description, image, URL) for a given document.
|
|
1288
|
+
*
|
|
1289
|
+
* @param pluginConfig - The plugin configuration containing generate functions
|
|
1290
|
+
* @returns A PayloadHandler for the generate endpoint
|
|
1291
|
+
*/
|
|
1292
|
+
declare function createGenerateHandler(pluginConfig: GeneratePluginConfig): PayloadHandler;
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Schema.org JSON-LD Auto-Generator endpoint.
|
|
1296
|
+
* Generates structured data (JSON-LD) from Payload document fields.
|
|
1297
|
+
* Supports: Article, LocalBusiness, BreadcrumbList, FAQPage, Product, Organization.
|
|
1298
|
+
*
|
|
1299
|
+
* Auto-detects schema type from collection/content, with optional override.
|
|
1300
|
+
*
|
|
1301
|
+
* NOTE: Rate limiting is not handled by this plugin. The consuming application
|
|
1302
|
+
* should implement rate limiting via its own middleware.
|
|
1303
|
+
*/
|
|
1304
|
+
|
|
1305
|
+
declare function createSchemaGeneratorHandler(targetCollections?: string[]): PayloadHandler;
|
|
1306
|
+
|
|
1307
|
+
/**
|
|
1308
|
+
* Redirect Chain Detection endpoint.
|
|
1309
|
+
* Reads all redirects from the seo-redirects collection, builds a graph,
|
|
1310
|
+
* and detects chains (A→B→C) and loops (A→B→A).
|
|
1311
|
+
*
|
|
1312
|
+
* NOTE: Rate limiting is not handled by this plugin. The consuming application
|
|
1313
|
+
* should implement rate limiting via its own middleware.
|
|
1314
|
+
*/
|
|
1315
|
+
|
|
1316
|
+
declare function createRedirectChainsHandler(redirectsCollection: string): PayloadHandler;
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Duplicate Content Detection endpoint (sitewide).
|
|
1320
|
+
* Compares pages pairwise using Jaccard similarity on word trigrams.
|
|
1321
|
+
* Flags pairs with >70% similarity.
|
|
1322
|
+
*
|
|
1323
|
+
* NOTE: Rate limiting is not handled by this plugin. The consuming application
|
|
1324
|
+
* should implement rate limiting via its own middleware.
|
|
1325
|
+
*/
|
|
1326
|
+
|
|
1327
|
+
declare function createDuplicateContentHandler(collections: string[]): PayloadHandler;
|
|
1328
|
+
|
|
1329
|
+
/**
|
|
1330
|
+
* AI Meta Rewrite endpoint.
|
|
1331
|
+
* Generates optimized meta title/description using either:
|
|
1332
|
+
* - Heuristic extraction (default, no API key needed)
|
|
1333
|
+
* - Claude AI API (when anthropicApiKey is provided)
|
|
1334
|
+
*
|
|
1335
|
+
* Accepts: collection, id, field (title|description), optional anthropicApiKey.
|
|
1336
|
+
*
|
|
1337
|
+
* NOTE: Rate limiting is not handled by this plugin. The consuming application
|
|
1338
|
+
* should implement rate limiting via its own middleware.
|
|
1339
|
+
*/
|
|
1340
|
+
|
|
1341
|
+
declare function createAiRewriteHandler(targetCollections?: string[]): PayloadHandler;
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* SEO Analyzer — Shared constants.
|
|
1345
|
+
* Centralizes thresholds, limits, and shared lists used across rule modules.
|
|
1346
|
+
* Importing from here ensures consistency and makes tuning easy.
|
|
1347
|
+
*
|
|
1348
|
+
* v1.4.0: Bilingual support (FR/EN) for all language-specific constants.
|
|
1349
|
+
*/
|
|
1350
|
+
declare const TITLE_LENGTH_MIN = 30;
|
|
1351
|
+
declare const TITLE_LENGTH_MAX = 60;
|
|
1352
|
+
declare const META_DESC_LENGTH_MIN = 120;
|
|
1353
|
+
declare const META_DESC_LENGTH_MAX = 160;
|
|
1354
|
+
declare const MIN_WORDS_POST = 800;
|
|
1355
|
+
declare const MIN_WORDS_FORM = 150;
|
|
1356
|
+
declare const MIN_WORDS_LEGAL = 200;
|
|
1357
|
+
declare const MIN_WORDS_GENERIC = 300;
|
|
1358
|
+
declare const MIN_WORDS_THIN = 100;
|
|
1359
|
+
declare const KEYWORD_DENSITY_MAX = 3;
|
|
1360
|
+
declare const KEYWORD_DENSITY_WARN = 2.5;
|
|
1361
|
+
declare const KEYWORD_DENSITY_MIN = 0.5;
|
|
1362
|
+
declare const MAX_RECURSION_DEPTH = 50;
|
|
1363
|
+
declare const SCORE_EXCELLENT = 91;
|
|
1364
|
+
declare const SCORE_GOOD = 71;
|
|
1365
|
+
declare const SCORE_OK = 41;
|
|
1366
|
+
declare const WARNING_MULTIPLIER = 0.5;
|
|
1367
|
+
/** Generic anchor texts that should be avoided in links */
|
|
1368
|
+
declare const GENERIC_ANCHORS: Record<'fr' | 'en', readonly string[]>;
|
|
1369
|
+
/** Standard utility page slugs where keyword-in-URL is not applicable */
|
|
1370
|
+
declare const UTILITY_SLUGS: Record<'fr' | 'en', readonly string[]>;
|
|
1371
|
+
/** Evergreen pages where freshness is less relevant */
|
|
1372
|
+
declare const EVERGREEN_SLUGS: Record<'fr' | 'en', readonly string[]>;
|
|
1373
|
+
/** Stop words (for slug analysis) */
|
|
1374
|
+
declare const STOP_WORDS: Record<'fr' | 'en', readonly string[]>;
|
|
1375
|
+
/** Action verbs commonly used in CTAs */
|
|
1376
|
+
declare const ACTION_VERBS: Record<'fr' | 'en', readonly string[]>;
|
|
1377
|
+
/** Compound expressions where stop words are meaningful */
|
|
1378
|
+
declare const STOP_WORD_COMPOUNDS_MAP: Record<'fr' | 'en', ReadonlyArray<readonly [string, string]>>;
|
|
1379
|
+
/** Power words that boost CTR in titles */
|
|
1380
|
+
declare const POWER_WORDS: Record<'fr' | 'en', readonly string[]>;
|
|
1381
|
+
/** Legal page slugs (used by detectPageType) */
|
|
1382
|
+
declare const LEGAL_SLUGS_MAP: Record<'fr' | 'en', readonly string[]>;
|
|
1383
|
+
declare const FLESCH_THRESHOLDS: Record<'fr' | 'en', {
|
|
1384
|
+
pass: number;
|
|
1385
|
+
warn: number;
|
|
1386
|
+
}>;
|
|
1387
|
+
declare const READABILITY_THRESHOLDS: Record<'fr' | 'en', {
|
|
1388
|
+
longSentenceWords: number;
|
|
1389
|
+
passiveMax: number;
|
|
1390
|
+
transitionsMin: number;
|
|
1391
|
+
}>;
|
|
1392
|
+
/** @deprecated Use POWER_WORDS.fr */
|
|
1393
|
+
declare const POWER_WORDS_FR: readonly string[];
|
|
1394
|
+
declare function getStopWords(locale: 'fr' | 'en'): readonly string[];
|
|
1395
|
+
declare function getActionVerbs(locale: 'fr' | 'en'): readonly string[];
|
|
1396
|
+
declare function getPowerWords(locale: 'fr' | 'en'): readonly string[];
|
|
1397
|
+
declare function getGenericAnchors(locale: 'fr' | 'en'): readonly string[];
|
|
1398
|
+
declare function getLegalSlugs(locale: 'fr' | 'en'): readonly string[];
|
|
1399
|
+
declare function getUtilitySlugs(locale: 'fr' | 'en'): readonly string[];
|
|
1400
|
+
declare function getEvergreenSlugs(locale: 'fr' | 'en'): readonly string[];
|
|
1401
|
+
declare function getStopWordCompounds(locale: 'fr' | 'en'): ReadonlyArray<readonly [string, string]>;
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* SEO Analyzer — Main orchestrator.
|
|
1405
|
+
* Imports all rule modules, builds the analysis context, runs every check,
|
|
1406
|
+
* and computes the final score.
|
|
1407
|
+
*
|
|
1408
|
+
* Public API:
|
|
1409
|
+
* analyzeSeo(data: SeoInput, config?: SeoConfig): SeoAnalysis
|
|
1410
|
+
*
|
|
1411
|
+
* All types and useful helpers are re-exported for convenience.
|
|
1412
|
+
*/
|
|
1413
|
+
|
|
1414
|
+
declare function analyzeSeo(data: SeoInput, config?: SeoConfig): SeoAnalysis;
|
|
1415
|
+
|
|
1416
|
+
export { ACTION_VERBS, type AnalysisContext, type CheckCategory, type CheckStatus, type DashboardLocale, type DashboardTranslations, type DocSourceType, EVERGREEN_SLUGS, FLESCH_THRESHOLDS, type FetchAllDocsOptions, type FetchedDoc, GENERIC_ANCHORS, type GenerateFnArgs$1 as GenerateFnArgs, KEYWORD_DENSITY_MAX, KEYWORD_DENSITY_MIN, KEYWORD_DENSITY_WARN, LEGAL_SLUGS_MAP, MAX_RECURSION_DEPTH, META_DESC_LENGTH_MAX, META_DESC_LENGTH_MIN, MIN_WORDS_FORM, MIN_WORDS_GENERIC, MIN_WORDS_LEGAL, MIN_WORDS_POST, MIN_WORDS_THIN, type MetaFieldsConfig, POWER_WORDS, POWER_WORDS_FR, type PageType, READABILITY_THRESHOLDS, type ResolveLocaleArgs, type RuleGroup, SCORE_EXCELLENT, SCORE_GOOD, SCORE_OK, STOP_WORDS, STOP_WORD_COMPOUNDS_MAP, type SeoAnalysis, type SeoCheck, type SeoConfig, type SeoFeatures, type SeoInput, type SeoLevel, type SeoPluginConfig, type SeoThresholds, TITLE_LENGTH_MAX, TITLE_LENGTH_MIN, UTILITY_SLUGS, WARNING_MULTIPLIER, analyzeSeo, buildSeoInputFromDoc, calculateFlesch, calculateFleschFR, checkHeadingHierarchy, checkImagesInBlocks, countKeywordOccurrences, countLongSections, countSentences, countSyllablesEN, countSyllablesFR, countWords, createAiRewriteHandler, createDuplicateContentHandler, createGenerateHandler, createHistoryHandler, createKeywordResearchHandler, createPerformanceHandler, createRedirectChainsHandler, createSchemaGeneratorHandler, createSeoPerformanceCollection, createSeoScoreHistoryCollection, createSitemapAuditHandler, createTrackSeoScoreHook, detectPageType, detectPassiveVoice, extractHeadingsFromLexical, extractImagesFromLexical, extractLinkUrlsFromLexical, extractLinksFromLexical, extractListsFromLexical, extractTextFromLexical, fetchAllDocs, getActionVerbs, getActionVerbsFR, getDashboardT, getEvergreenSlugs, getGenericAnchors, getLegalSlugs, getPowerWords, getStopWordCompounds, getStopWords, getStopWordsFR, getUtilitySlugs, hasTransitionWord, isStopWordInCompoundExpression, keywordMatchesText, metaFields, normalizeForComparison, registerDashboardTranslations, resolveAnalysisLocale, seoAnalyzerPlugin, seoFields, seoAnalyzerPlugin as seoPlugin, slugifyKeyword };
|