@vicacha-devs/strapi-plugin-seo 0.0.0-stage → 2.1.0

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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -2
  3. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/AlternativeTextCheck/index.d.ts +9 -0
  4. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/CanonicalUrlCheck/index.d.ts +7 -0
  5. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/KeywordDensityCheck/index.d.ts +7 -0
  6. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/LastUpdatedAtCheck/index.d.ts +7 -0
  7. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/MetaDescriptionCheck/index.d.ts +7 -0
  8. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/MetaRobotCheck/index.d.ts +7 -0
  9. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/MetaTitleCheck/index.d.ts +7 -0
  10. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/OpenGraphCheck/index.d.ts +7 -0
  11. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/SEOAccordion/index.d.ts +10 -0
  12. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/StructuredDataCheck/index.d.ts +7 -0
  13. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/WordCountCheck/index.d.ts +7 -0
  14. package/dist/admin/components/CMEditView/RightLinksCompo/SeoChecks/index.d.ts +10 -0
  15. package/dist/admin/components/CMEditView/RightLinksCompo/Summary/BrowserPreview/KeywordCheck/index.d.ts +7 -0
  16. package/dist/admin/components/CMEditView/RightLinksCompo/Summary/BrowserPreview/MetaChecks/index.d.ts +8 -0
  17. package/dist/admin/components/CMEditView/RightLinksCompo/Summary/BrowserPreview/Serp/index.d.ts +6 -0
  18. package/dist/admin/components/CMEditView/RightLinksCompo/Summary/BrowserPreview/index.d.ts +6 -0
  19. package/dist/admin/components/CMEditView/RightLinksCompo/Summary/OpenGraphPreview/Facebook/index.d.ts +8 -0
  20. package/dist/admin/components/CMEditView/RightLinksCompo/Summary/OpenGraphPreview/LinkedIn/index.d.ts +8 -0
  21. package/dist/admin/components/CMEditView/RightLinksCompo/Summary/OpenGraphPreview/X/index.d.ts +8 -0
  22. package/dist/admin/components/CMEditView/RightLinksCompo/Summary/OpenGraphPreview/index.d.ts +6 -0
  23. package/dist/admin/components/CMEditView/RightLinksCompo/Summary/PreviewChecks/index.d.ts +6 -0
  24. package/dist/admin/components/CMEditView/RightLinksCompo/Summary/index.d.ts +4 -0
  25. package/dist/admin/components/CMEditView/RightLinksCompo/Summary/reducer.d.ts +2 -0
  26. package/dist/admin/components/CMEditView/RightLinksCompo/index.d.ts +1 -0
  27. package/dist/admin/components/CMEditView/utils/checks.d.ts +14 -0
  28. package/dist/admin/components/CMEditView/utils/getRegularImageAltTexts.d.ts +5 -0
  29. package/dist/admin/components/CMEditView/utils/getRichTextAndBlocksFields.d.ts +10 -0
  30. package/dist/admin/components/CMEditView/utils/getRichTextData.d.ts +2 -0
  31. package/dist/admin/components/HomePage/Header/index.d.ts +5 -0
  32. package/dist/admin/components/HomePage/Main/EmptyComponentLayout/illo.d.ts +1 -0
  33. package/dist/admin/components/HomePage/Main/SettingsModal/index.d.ts +8 -0
  34. package/dist/admin/components/HomePage/Main/index.d.ts +6 -0
  35. package/dist/admin/components/Initializer.d.ts +5 -0
  36. package/dist/admin/hooks/useSettingsApi.d.ts +5 -0
  37. package/dist/admin/index-C8tiCtgn.cjs +3490 -0
  38. package/dist/admin/index-CJ64dp6B.js +3473 -0
  39. package/dist/admin/index-CZ4VBOGS.cjs +589 -0
  40. package/dist/admin/index-DgvE6ytW.js +571 -0
  41. package/dist/admin/index.cjs +5 -0
  42. package/dist/admin/index.d.ts +18 -0
  43. package/dist/admin/index.js +5 -0
  44. package/dist/admin/index.mjs +5 -0
  45. package/dist/admin/pages/App/index.d.ts +2 -0
  46. package/dist/admin/pages/HomePage/index.d.ts +2 -0
  47. package/dist/admin/permissions.d.ts +10 -0
  48. package/dist/admin/pluginId.d.ts +1 -0
  49. package/dist/admin/translations/ar.json.d.ts +133 -0
  50. package/dist/admin/translations/en.json.d.ts +133 -0
  51. package/dist/admin/translations/es.json.d.ts +133 -0
  52. package/dist/admin/translations/fr.json.d.ts +133 -0
  53. package/dist/admin/translations/pt-BR.json.d.ts +133 -0
  54. package/dist/admin/translations/tr.json.d.ts +133 -0
  55. package/dist/admin/utils/getTrad.d.ts +1 -0
  56. package/dist/admin/utils/prefixPluginTranslations.d.ts +1 -0
  57. package/dist/package.json +3 -0
  58. package/dist/server/bootstrap.d.ts +5 -0
  59. package/dist/server/components/open-graph.json.d.ts +36 -0
  60. package/dist/server/components/seo.json.d.ts +52 -0
  61. package/dist/server/config/index.d.ts +5 -0
  62. package/dist/server/controllers/index.d.ts +16 -0
  63. package/dist/server/controllers/seo.d.ts +13 -0
  64. package/dist/server/controllers/settings.d.ts +16 -0
  65. package/dist/server/destroy.d.ts +5 -0
  66. package/dist/server/index.cjs +416 -0
  67. package/dist/server/index.d.ts +83 -0
  68. package/dist/server/index.js +416 -0
  69. package/dist/server/index.mjs +416 -0
  70. package/dist/server/register.d.ts +5 -0
  71. package/dist/server/routes/index.d.ts +35 -0
  72. package/dist/server/routes/seo.d.ts +16 -0
  73. package/dist/server/routes/settings.d.ts +16 -0
  74. package/dist/server/services/index.d.ts +19 -0
  75. package/dist/server/services/seo.d.ts +12 -0
  76. package/dist/server/services/settings.d.ts +10 -0
  77. package/dist/shared/index.cjs +17 -0
  78. package/dist/shared/index.d.ts +1 -0
  79. package/dist/shared/index.js +17 -0
  80. package/dist/shared/index.mjs +17 -0
  81. package/package.json +100 -3
@@ -0,0 +1,3490 @@
1
+ "use strict";
2
+ const icons = require("@strapi/icons");
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const admin = require("@strapi/strapi/admin");
5
+ const React = require("react");
6
+ const designSystem = require("@strapi/design-system");
7
+ const reactIntl = require("react-intl");
8
+ const dateFns = require("date-fns");
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
12
+ if (e) {
13
+ for (const k in e) {
14
+ if (k !== "default") {
15
+ const d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: () => e[k]
19
+ });
20
+ }
21
+ }
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+ const React__namespace = /* @__PURE__ */ _interopNamespace(React);
27
+ function freezeOptions(items) {
28
+ return Object.freeze(items.map((item) => Object.freeze({ ...item })));
29
+ }
30
+ freezeOptions([
31
+ { value: "es", label: "es — Español (Spanish)" },
32
+ { value: "en", label: "en — English" },
33
+ { value: "ar", label: "ar — العربية (Arabic)" },
34
+ { value: "tr", label: "tr — Türkçe (Turkish)" },
35
+ { value: "fr", label: "fr — Français (French)" },
36
+ { value: "de", label: "de — Deutsch (German)" },
37
+ { value: "pt", label: "pt — Português (Portuguese)" },
38
+ { value: "it", label: "it — Italiano (Italian)" },
39
+ { value: "nl", label: "nl — Nederlands (Dutch)" },
40
+ { value: "pl", label: "pl — Polski (Polish)" },
41
+ { value: "ru", label: "ru — Русский (Russian)" },
42
+ { value: "id", label: "id — Bahasa Indonesia" },
43
+ { value: "ur", label: "ur — اردو (Urdu)" },
44
+ { value: "zh", label: "zh — 中文 (Chinese)" }
45
+ ]);
46
+ freezeOptions([
47
+ {
48
+ value: "islamic-umalqura",
49
+ label: "islamic-umalqura (Hijri Umm al-Qura)"
50
+ },
51
+ { value: "islamic-civil", label: "islamic-civil (Tabular Islamic Civil)" },
52
+ { value: "islamic-tbla", label: "islamic-tbla (Tabular Astronomical)" },
53
+ { value: "hebrew", label: "hebrew (Hebrew)" },
54
+ { value: "persian", label: "persian (Solar Hijri)" },
55
+ { value: "none", label: "none (Gregorian Only)" }
56
+ ]);
57
+ const VALID_LOCALE_RE = /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8}){0,4}$/;
58
+ function isTranslationRecord(value) {
59
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
60
+ }
61
+ function extractDictionary(loaded) {
62
+ if (!isTranslationRecord(loaded)) {
63
+ return void 0;
64
+ }
65
+ if ("default" in loaded) {
66
+ return isTranslationRecord(loaded.default) ? loaded.default : void 0;
67
+ }
68
+ return loaded;
69
+ }
70
+ function getLookupCandidates(locale, fallbackToLanguage) {
71
+ const trimmed = locale.trim();
72
+ if (!VALID_LOCALE_RE.test(trimmed)) {
73
+ return [];
74
+ }
75
+ if (!fallbackToLanguage || !trimmed.includes("-")) {
76
+ return [trimmed];
77
+ }
78
+ const base = trimmed.split("-")[0];
79
+ return base && base !== trimmed ? [trimmed, base] : [trimmed];
80
+ }
81
+ function prefixPluginTranslations(trad, pluginId2, options) {
82
+ const normalizedPluginId = typeof pluginId2 === "string" ? pluginId2.trim() : "";
83
+ if (!normalizedPluginId) {
84
+ throw new TypeError("pluginId can't be empty");
85
+ }
86
+ const result = {};
87
+ if (options?.baseTranslations) {
88
+ for (const [key, value] of Object.entries(options.baseTranslations)) {
89
+ if (typeof value === "string") {
90
+ result[key] = value;
91
+ }
92
+ }
93
+ }
94
+ const prefix = `${normalizedPluginId}.`;
95
+ const explicitPrefixed = [];
96
+ for (const [key, value] of Object.entries(trad)) {
97
+ if (typeof value !== "string") {
98
+ continue;
99
+ }
100
+ if (options?.keepUnprefixed) {
101
+ result[key] = value;
102
+ }
103
+ if (key.startsWith(prefix)) {
104
+ explicitPrefixed.push([key, value]);
105
+ } else {
106
+ result[`${prefix}${key}`] = value;
107
+ }
108
+ }
109
+ for (const [key, value] of explicitPrefixed) {
110
+ result[key] = value;
111
+ }
112
+ return result;
113
+ }
114
+ function createRegisterTrads(options) {
115
+ const { pluginId: pluginId2, defaultLocales = ["en", "es", "ar", "tr"], supportedLocales, keepUnprefixed = true, fallbackToLanguageCode = true, loadTranslation, translations, getBaseTranslations, onLoaded } = options;
116
+ const normalizedPluginId = typeof pluginId2 === "string" ? pluginId2.trim() : "";
117
+ if (!normalizedPluginId) {
118
+ throw new TypeError("pluginId can't be empty");
119
+ }
120
+ const supportedSet = supportedLocales ? new Set(supportedLocales) : void 0;
121
+ const effectiveDefaults = supportedSet ? defaultLocales.filter((loc) => getLookupCandidates(loc, fallbackToLanguageCode).some((candidate) => supportedSet.has(candidate))) : defaultLocales;
122
+ return async function registerTrads({ locales = [] } = {}) {
123
+ const allLocales = effectiveDefaults.length > 0 ? Array.from(/* @__PURE__ */ new Set([...locales, ...effectiveDefaults])) : Array.from(new Set(locales));
124
+ return Promise.all(allLocales.map(async (locale) => {
125
+ const candidates = getLookupCandidates(locale, fallbackToLanguageCode);
126
+ if (candidates.length === 0) {
127
+ return { data: {}, locale };
128
+ }
129
+ if (supportedSet && !candidates.some((candidate) => supportedSet.has(candidate))) {
130
+ return { data: {}, locale };
131
+ }
132
+ let rawData;
133
+ for (const candidate of candidates) {
134
+ if (translations && Object.hasOwn(translations, candidate)) {
135
+ rawData = extractDictionary(translations[candidate]);
136
+ if (rawData) {
137
+ break;
138
+ }
139
+ }
140
+ if (loadTranslation) {
141
+ try {
142
+ const loaded = await loadTranslation(candidate);
143
+ rawData = extractDictionary(loaded);
144
+ if (rawData) {
145
+ break;
146
+ }
147
+ } catch {
148
+ }
149
+ }
150
+ }
151
+ const baseTranslations = getBaseTranslations?.(locale);
152
+ const hasBase = Boolean(baseTranslations) && Object.keys(baseTranslations).length > 0;
153
+ if (!rawData && !hasBase) {
154
+ return { data: {}, locale };
155
+ }
156
+ const prefixed = prefixPluginTranslations(rawData ?? {}, normalizedPluginId, {
157
+ keepUnprefixed,
158
+ baseTranslations
159
+ });
160
+ onLoaded?.(locale, prefixed);
161
+ return { data: prefixed, locale };
162
+ }));
163
+ };
164
+ }
165
+ const strapi = { "name": "seo" };
166
+ const pluginPkg = {
167
+ strapi
168
+ };
169
+ const DEFAULT_SEO_CHECKS = {
170
+ metaTitle: true,
171
+ metaDescription: true,
172
+ metaRobots: true,
173
+ openGraph: true,
174
+ wordCount: true,
175
+ canonicalUrl: true,
176
+ keywordDensity: true,
177
+ structuredData: true,
178
+ alternativeText: true,
179
+ lastUpdatedAt: true
180
+ };
181
+ const pluginId = pluginPkg.strapi?.name;
182
+ const useSettingsApi = () => {
183
+ const { get, post } = admin.useFetchClient();
184
+ const getSettings = async () => {
185
+ return get(`/${pluginId}/settings`);
186
+ };
187
+ const setSettings = async (settings) => {
188
+ return post(`/${pluginId}/settings`, settings);
189
+ };
190
+ return {
191
+ getSettings,
192
+ setSettings
193
+ };
194
+ };
195
+ const getTrad = (id) => `${pluginId}.${id}`;
196
+ const qualityVerdict = {
197
+ good: "good",
198
+ improvements: "improvements",
199
+ bad: "bad"
200
+ };
201
+ const isEmptyValue = (value) => {
202
+ if (value === null || value === void 0) return true;
203
+ if (typeof value === "string") return value.trim().length === 0;
204
+ if (Array.isArray(value)) return value.length === 0;
205
+ if (typeof value === "object") {
206
+ return Object.keys(value).length === 0;
207
+ }
208
+ return false;
209
+ };
210
+ const isSameCheckStatus = (a, b) => {
211
+ if (!a || !b) return false;
212
+ return a.qualityVerdict === b.qualityVerdict && a.message === b.message;
213
+ };
214
+ const getMetaTitleCheckPreview = (modifiedData) => {
215
+ const metaTitle = modifiedData?.seo?.metaTitle ?? null;
216
+ if (typeof metaTitle !== "string" || isEmptyValue(metaTitle)) {
217
+ return {
218
+ message: "",
219
+ qualityVerdict: qualityVerdict.improvements
220
+ };
221
+ }
222
+ if (metaTitle.length > 60) {
223
+ return {
224
+ message: "",
225
+ qualityVerdict: qualityVerdict.bad
226
+ };
227
+ }
228
+ return {
229
+ message: "",
230
+ qualityVerdict: qualityVerdict.good
231
+ };
232
+ };
233
+ const getMetaDescriptionPreview = (modifiedData) => {
234
+ const metaDescription = modifiedData?.seo?.metaDescription ?? null;
235
+ if (typeof metaDescription !== "string" || isEmptyValue(metaDescription)) {
236
+ return {
237
+ message: "",
238
+ qualityVerdict: qualityVerdict.improvements
239
+ };
240
+ }
241
+ if (metaDescription.length > 160 || metaDescription.length < 50) {
242
+ return {
243
+ message: "",
244
+ qualityVerdict: qualityVerdict.bad
245
+ };
246
+ }
247
+ return {
248
+ message: "",
249
+ qualityVerdict: qualityVerdict.good
250
+ };
251
+ };
252
+ const getAlternativeTextPreview = (emptyAltCount) => {
253
+ const intersections = emptyAltCount?.intersections ?? null;
254
+ const richTextAlts = Array.isArray(emptyAltCount?.richTextAlts) ? emptyAltCount.richTextAlts : [];
255
+ const blockTextAlts = Array.isArray(emptyAltCount?.blockTextAlts) ? emptyAltCount.blockTextAlts : [];
256
+ const altTexts = Array.isArray(emptyAltCount?.altTexts) ? emptyAltCount.altTexts : [];
257
+ const missingRichOrBlockAlt = [...richTextAlts, ...blockTextAlts].filter(
258
+ (x) => x.occurences !== 0
259
+ ).length;
260
+ if (intersections === 0) {
261
+ return {
262
+ message: "",
263
+ qualityVerdict: qualityVerdict.bad
264
+ };
265
+ }
266
+ if (altTexts.includes("") || missingRichOrBlockAlt >= 1) {
267
+ return {
268
+ message: "",
269
+ qualityVerdict: qualityVerdict.improvements
270
+ };
271
+ }
272
+ return {
273
+ message: "",
274
+ qualityVerdict: qualityVerdict.good
275
+ };
276
+ };
277
+ const getWordCountPreview = (wordCount) => {
278
+ if (typeof wordCount !== "number" || wordCount < 300) {
279
+ return {
280
+ message: "",
281
+ qualityVerdict: qualityVerdict.improvements
282
+ };
283
+ }
284
+ return {
285
+ message: "",
286
+ qualityVerdict: qualityVerdict.good
287
+ };
288
+ };
289
+ const getKeywordDensityPreview = (keywordsDensity) => {
290
+ if (isEmptyValue(keywordsDensity)) {
291
+ return {
292
+ message: "",
293
+ qualityVerdict: qualityVerdict.improvements
294
+ };
295
+ }
296
+ let hasUnused = false;
297
+ let hasUsedOnce = false;
298
+ Object.keys(keywordsDensity).forEach((keyword) => {
299
+ const count = keywordsDensity[keyword]?.count ?? 0;
300
+ if (count === 0) {
301
+ hasUnused = true;
302
+ } else if (count <= 1) {
303
+ hasUsedOnce = true;
304
+ }
305
+ });
306
+ if (hasUsedOnce) {
307
+ return {
308
+ message: "",
309
+ qualityVerdict: qualityVerdict.bad
310
+ };
311
+ }
312
+ if (hasUnused) {
313
+ return {
314
+ message: "",
315
+ qualityVerdict: qualityVerdict.improvements
316
+ };
317
+ }
318
+ return {
319
+ message: "",
320
+ qualityVerdict: qualityVerdict.good
321
+ };
322
+ };
323
+ const canonicalUrlPreview = (modifiedData) => {
324
+ const canonicalUrl = modifiedData?.seo?.canonicalURL ?? null;
325
+ if (typeof canonicalUrl !== "string" || isEmptyValue(canonicalUrl)) {
326
+ return {
327
+ message: "",
328
+ qualityVerdict: qualityVerdict.bad
329
+ };
330
+ }
331
+ return {
332
+ message: "",
333
+ qualityVerdict: qualityVerdict.good
334
+ };
335
+ };
336
+ const lastUpdatedAtPreview = (modifiedData, fallbackUpdatedAt = null) => {
337
+ const updatedAt = fallbackUpdatedAt ?? modifiedData?.updatedAt ?? null;
338
+ if (typeof updatedAt !== "string" || isEmptyValue(updatedAt)) {
339
+ return {
340
+ message: "",
341
+ qualityVerdict: qualityVerdict.bad
342
+ };
343
+ }
344
+ const parsedUpdatedAt = Date.parse(updatedAt);
345
+ if (Number.isNaN(parsedUpdatedAt)) {
346
+ return {
347
+ message: "",
348
+ qualityVerdict: qualityVerdict.bad
349
+ };
350
+ }
351
+ const oneYearAgoDate = /* @__PURE__ */ new Date();
352
+ oneYearAgoDate.setFullYear(oneYearAgoDate.getFullYear() - 1);
353
+ if (parsedUpdatedAt >= oneYearAgoDate.getTime()) {
354
+ return {
355
+ message: "",
356
+ qualityVerdict: qualityVerdict.good
357
+ };
358
+ }
359
+ return {
360
+ message: "",
361
+ qualityVerdict: qualityVerdict.improvements
362
+ };
363
+ };
364
+ const metaRobotPreview = (modifiedData) => {
365
+ const metaRobots = modifiedData?.seo?.metaRobots ?? null;
366
+ if (typeof metaRobots !== "string" || isEmptyValue(metaRobots)) {
367
+ return {
368
+ message: "",
369
+ qualityVerdict: qualityVerdict.good
370
+ };
371
+ }
372
+ return {
373
+ message: "",
374
+ qualityVerdict: qualityVerdict.good
375
+ };
376
+ };
377
+ const openGraphPreview = (modifiedData) => {
378
+ const openGraph = modifiedData?.seo?.openGraph ?? null;
379
+ if (!openGraph || typeof openGraph !== "object") {
380
+ return {
381
+ message: "",
382
+ qualityVerdict: qualityVerdict.bad
383
+ };
384
+ }
385
+ if (isEmptyValue(openGraph.ogTitle) || isEmptyValue(openGraph.ogDescription) || !openGraph.ogImage) {
386
+ return {
387
+ message: "",
388
+ qualityVerdict: qualityVerdict.improvements
389
+ };
390
+ }
391
+ return {
392
+ message: "",
393
+ qualityVerdict: qualityVerdict.good
394
+ };
395
+ };
396
+ const structuredDataPreview = (modifiedData) => {
397
+ const structuredData = modifiedData?.seo?.structuredData ?? null;
398
+ if (isEmptyValue(structuredData)) {
399
+ return {
400
+ message: "",
401
+ qualityVerdict: qualityVerdict.bad
402
+ };
403
+ }
404
+ return {
405
+ message: "",
406
+ qualityVerdict: qualityVerdict.good
407
+ };
408
+ };
409
+ const recursiveSearch = (obj, keyword, relations, results = [], allImageNames = [], visited = /* @__PURE__ */ new WeakSet()) => {
410
+ const alternativeTexts = results;
411
+ const imageNames = allImageNames;
412
+ if (!obj || typeof obj !== "object" || visited.has(obj)) {
413
+ return { alternativeTexts, imageNames };
414
+ }
415
+ visited.add(obj);
416
+ Object.keys(obj).forEach((key) => {
417
+ const value = obj[key];
418
+ if (key === keyword && (value === null || typeof value !== "object")) {
419
+ alternativeTexts.push(typeof value === "string" ? value : "");
420
+ imageNames.push(typeof obj.name === "string" ? obj.name : "");
421
+ } else if (value !== null && typeof value === "object" && !relations.includes(key)) {
422
+ recursiveSearch(
423
+ value,
424
+ keyword,
425
+ relations,
426
+ alternativeTexts,
427
+ imageNames,
428
+ visited
429
+ );
430
+ }
431
+ });
432
+ return { alternativeTexts, imageNames };
433
+ };
434
+ const getRegularImageAltTexts = (contentType, modifiedData) => {
435
+ const relations = ["localizations"];
436
+ const attributes = contentType?.attributes ?? {};
437
+ Object.keys(attributes).forEach((field) => {
438
+ if (attributes[field]?.type === "relation") {
439
+ relations.push(field);
440
+ }
441
+ });
442
+ const { alternativeTexts, imageNames } = recursiveSearch(
443
+ modifiedData ?? {},
444
+ "alternativeText",
445
+ relations
446
+ );
447
+ const alternativeTextCount = alternativeTexts.length;
448
+ const intersections = alternativeTexts.filter((altText) => imageNames.includes(altText)).length - alternativeTextCount;
449
+ return { intersections, altTexts: alternativeTexts };
450
+ };
451
+ const isPlainObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
452
+ const matchesFieldType = (fieldProperties, fieldType) => {
453
+ if (!fieldProperties) return false;
454
+ if (fieldProperties.type === fieldType) return true;
455
+ if (fieldType === "richtext" && fieldProperties.type === "customField" && typeof fieldProperties.customField === "string" && fieldProperties.customField.toLowerCase().includes("richtext")) {
456
+ return true;
457
+ }
458
+ return false;
459
+ };
460
+ const collectFieldsByType = (contentType, components, fieldType) => {
461
+ const fields = [];
462
+ const attributes = contentType?.attributes ?? {};
463
+ const safeComponents = components ?? {};
464
+ Object.entries(attributes).forEach(([field, fieldProperties]) => {
465
+ if (matchesFieldType(fieldProperties, fieldType)) {
466
+ fields.push({ name: field, field: null });
467
+ } else if (fieldProperties?.type === "component" && fieldProperties.component) {
468
+ const compAttrs = safeComponents[fieldProperties.component]?.attributes ?? {};
469
+ Object.entries(compAttrs).forEach(([field2, fieldProperties2]) => {
470
+ if (matchesFieldType(fieldProperties2, fieldType)) {
471
+ fields.push({ name: field, field: field2 });
472
+ }
473
+ });
474
+ }
475
+ });
476
+ Object.entries(safeComponents).forEach(([componentName, componentData]) => {
477
+ const compAttrs = componentData?.attributes ?? {};
478
+ Object.entries(compAttrs).forEach(([field, fieldProperties]) => {
479
+ if (matchesFieldType(fieldProperties, fieldType)) {
480
+ fields.push({ name: componentName, field });
481
+ }
482
+ });
483
+ });
484
+ return fields;
485
+ };
486
+ const resolveAndDeduplicateFields = (fields, dynamicZones, modifiedData) => {
487
+ const updated = fields.map((entry) => ({ ...entry }));
488
+ const removedIndices = /* @__PURE__ */ new Set();
489
+ dynamicZones.forEach((dz) => {
490
+ const rawDz = modifiedData?.[dz];
491
+ const dynamicZoneData = Array.isArray(rawDz) ? rawDz.filter(isPlainObject) : [];
492
+ updated.forEach((field, index2) => {
493
+ if (removedIndices.has(index2)) return;
494
+ const componentExistsInDZ = dynamicZoneData.filter(
495
+ (x) => x.__component === field.name
496
+ );
497
+ if (componentExistsInDZ.length > 0 && !field.inDz && field.field && dynamicZoneData.some(
498
+ (x) => Object.prototype.hasOwnProperty.call(x, field.field)
499
+ )) {
500
+ updated[index2] = { ...field, inDz: dz };
501
+ } else if (componentExistsInDZ.length === 0 && field.inDz === dz) {
502
+ removedIndices.add(index2);
503
+ }
504
+ });
505
+ });
506
+ const seenKeys = /* @__PURE__ */ new Set();
507
+ const deduplicated = [];
508
+ updated.forEach((field, index2) => {
509
+ if (removedIndices.has(index2)) return;
510
+ if (field.field === null) {
511
+ const key2 = `root:${field.name}`;
512
+ if (!seenKeys.has(key2)) {
513
+ seenKeys.add(key2);
514
+ deduplicated.push(field);
515
+ }
516
+ return;
517
+ }
518
+ if (field.inDz) {
519
+ const key2 = `dz:${field.inDz}:${field.name}:${field.field}`;
520
+ if (!seenKeys.has(key2)) {
521
+ seenKeys.add(key2);
522
+ deduplicated.push(field);
523
+ }
524
+ return;
525
+ }
526
+ const compKey = field.name.split(".").pop() || field.name;
527
+ if (field.name.includes(".") && modifiedData?.[compKey] === void 0 && modifiedData?.[field.name] === void 0) {
528
+ return;
529
+ }
530
+ const key = `comp:${compKey}:${field.field}`;
531
+ if (!seenKeys.has(key)) {
532
+ seenKeys.add(key);
533
+ deduplicated.push(field);
534
+ }
535
+ });
536
+ return deduplicated;
537
+ };
538
+ const getRichTextAndBlocksFields = (contentType, components, modifiedData) => {
539
+ const dynamicZones = [];
540
+ Object.entries(modifiedData ?? {}).forEach(([fieldName, fieldValue]) => {
541
+ if (Array.isArray(fieldValue)) {
542
+ const isComponentField = fieldValue.some(
543
+ (subField) => isPlainObject(subField) && "__component" in subField
544
+ );
545
+ if (isComponentField) dynamicZones.push(fieldName);
546
+ }
547
+ });
548
+ const rawRichTextFields = collectFieldsByType(
549
+ contentType,
550
+ components,
551
+ "richtext"
552
+ );
553
+ const rawBlockFields = collectFieldsByType(contentType, components, "blocks");
554
+ return {
555
+ richTextFields: resolveAndDeduplicateFields(
556
+ rawRichTextFields,
557
+ dynamicZones,
558
+ modifiedData
559
+ ),
560
+ blockFields: resolveAndDeduplicateFields(
561
+ rawBlockFields,
562
+ dynamicZones,
563
+ modifiedData
564
+ )
565
+ };
566
+ };
567
+ const extractTextFromBlocks = (blocks) => {
568
+ let textContent = "";
569
+ blocks.forEach((block) => {
570
+ if (!block || typeof block !== "object") return;
571
+ if (block.type === "text" && typeof block.text === "string") {
572
+ textContent += `${block.text} `;
573
+ } else if (Array.isArray(block.children)) {
574
+ textContent += `${extractTextFromBlocks(block.children)} `;
575
+ } else if (Array.isArray(block.content)) {
576
+ textContent += `${extractTextFromBlocks(block.content)} `;
577
+ }
578
+ });
579
+ return textContent.trim();
580
+ };
581
+ const isImageElementMissingAlt = (element) => {
582
+ if (element.type !== "image" || !element.image) return false;
583
+ const alt = element.image.alternativeText;
584
+ return typeof alt !== "string" || alt.trim().length === 0;
585
+ };
586
+ const isMediaAttrElementMissingAlt = (element) => {
587
+ if (element.type === "image" && element.image) return false;
588
+ const isMediaType = element.type === "image" || element.type === "mediaLibraryImage";
589
+ if (!isMediaType || !element.attrs) return false;
590
+ const alt = element.attrs.alt ?? element.attrs.alternativeText;
591
+ return typeof alt !== "string" || alt.trim().length === 0;
592
+ };
593
+ const countMissingBlockImageAlts = (nodes) => {
594
+ let missingCount = 0;
595
+ nodes.forEach((element) => {
596
+ if (!element || typeof element !== "object") return;
597
+ if (isImageElementMissingAlt(element) || isMediaAttrElementMissingAlt(element)) {
598
+ missingCount += 1;
599
+ }
600
+ if (Array.isArray(element.children)) {
601
+ missingCount += countMissingBlockImageAlts(element.children);
602
+ }
603
+ if (Array.isArray(element.content)) {
604
+ missingCount += countMissingBlockImageAlts(element.content);
605
+ }
606
+ });
607
+ return missingCount;
608
+ };
609
+ const extractNodesFromParsedObject = (value) => {
610
+ if (Array.isArray(value)) {
611
+ return value;
612
+ }
613
+ if (value && typeof value === "object") {
614
+ const obj = value;
615
+ if (Array.isArray(obj.content)) {
616
+ return obj.content;
617
+ }
618
+ }
619
+ return null;
620
+ };
621
+ const tryParseJsonStringNodes = (rawString) => {
622
+ const trimmed = rawString.trim();
623
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
624
+ return null;
625
+ }
626
+ try {
627
+ return extractNodesFromParsedObject(JSON.parse(trimmed));
628
+ } catch {
629
+ return null;
630
+ }
631
+ };
632
+ const tryParseRichTextNodes = (rawValue) => {
633
+ if (typeof rawValue === "string") {
634
+ return tryParseJsonStringNodes(rawValue);
635
+ }
636
+ return extractNodesFromParsedObject(rawValue);
637
+ };
638
+ const formatAltFieldName = (field, name2) => {
639
+ if (!name2) return field;
640
+ if (name2.includes(".")) {
641
+ return `[dynamic-zone].${name2.split(".").pop()}.${field}`;
642
+ }
643
+ return `${name2}.${field}`;
644
+ };
645
+ const hasNonEmptyHtmlAlt = (imgTag) => {
646
+ const match = /\balt\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/i.exec(
647
+ imgTag
648
+ );
649
+ const val = match?.[1] ?? match?.[2] ?? match?.[3] ?? "";
650
+ return val.trim().length > 0;
651
+ };
652
+ const getEmptyAltCount = (richtext, field, name2, isBlock = false) => {
653
+ let htmlOccurrences = 0;
654
+ let markdownOccurrences = 0;
655
+ let blockOccurences = 0;
656
+ if (isBlock && Array.isArray(richtext)) {
657
+ blockOccurences = countMissingBlockImageAlts(
658
+ richtext
659
+ );
660
+ } else if (typeof richtext === "string" && richtext) {
661
+ const emptyMdImages = richtext.match(/!\[\s*\]\([^()[\]]*\)/g);
662
+ markdownOccurrences = emptyMdImages ? emptyMdImages.length : 0;
663
+ const htmlImages = richtext.match(/<img\b[^<>]*>/gi) || [];
664
+ htmlOccurrences = htmlImages.filter(
665
+ (image) => !hasNonEmptyHtmlAlt(image)
666
+ ).length;
667
+ }
668
+ return {
669
+ field: formatAltFieldName(field, name2),
670
+ occurences: markdownOccurrences + htmlOccurrences + blockOccurences
671
+ };
672
+ };
673
+ const stripMarkdownAndHtmlToWords = (input, isPlainTree) => {
674
+ const withoutMarkdown = isPlainTree ? input : input.replace(/!\[/g, " [").replace(/\[([^[\]]*)\]\([^()[\]]*\)/g, " $1 ").replace(/<[^<>]+>/g, " ").replace(/[#>*_`~-]+/g, " ");
675
+ return withoutMarkdown.replace(/\s+/g, " ").trim().toLowerCase().split(" ").filter((entry) => entry.length > 0);
676
+ };
677
+ const increaseCounter = (base, field, isBlock = false, name2 = null) => {
678
+ let richtext;
679
+ let emptyAlts;
680
+ let parsedAsTree = isBlock;
681
+ const rawValue = base?.[field];
682
+ const treeNodes = tryParseRichTextNodes(rawValue);
683
+ if (isBlock || treeNodes) {
684
+ const blocks = treeNodes ?? [];
685
+ richtext = extractTextFromBlocks(blocks);
686
+ emptyAlts = getEmptyAltCount(blocks, field, name2, true);
687
+ parsedAsTree = true;
688
+ } else {
689
+ richtext = typeof rawValue === "string" ? rawValue : "";
690
+ emptyAlts = getEmptyAltCount(richtext, field, name2);
691
+ }
692
+ if (richtext) {
693
+ const words = stripMarkdownAndHtmlToWords(richtext, parsedAsTree);
694
+ return { words, length: words.length, emptyAlts };
695
+ }
696
+ return { words: [], length: 0, emptyAlts };
697
+ };
698
+ const buildKeywordDensityObject = (keywords, words, keywordsDensity) => {
699
+ const tempKeywordsDensity = {};
700
+ Object.keys(keywordsDensity).forEach((key) => {
701
+ tempKeywordsDensity[key] = { count: keywordsDensity[key].count };
702
+ });
703
+ const joinedWords = words.join(" ");
704
+ keywords.forEach((keyword) => {
705
+ const trimmedKeyword = keyword.trim();
706
+ if (!trimmedKeyword) return;
707
+ const regex = new RegExp(
708
+ trimmedKeyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
709
+ "g"
710
+ );
711
+ const count = joinedWords ? (joinedWords.match(regex) || []).length : 0;
712
+ if (!Object.prototype.hasOwnProperty.call(tempKeywordsDensity, trimmedKeyword)) {
713
+ tempKeywordsDensity[trimmedKeyword] = { count };
714
+ } else {
715
+ tempKeywordsDensity[trimmedKeyword] = {
716
+ count: tempKeywordsDensity[trimmedKeyword].count + count
717
+ };
718
+ }
719
+ });
720
+ return tempKeywordsDensity;
721
+ };
722
+ const adjustIntersections = (data) => {
723
+ const { emptyAltCount } = data;
724
+ let occurrences = 0;
725
+ emptyAltCount.richTextAlts.forEach((alt) => {
726
+ occurrences += alt.occurences;
727
+ });
728
+ emptyAltCount.blockTextAlts.forEach((alt) => {
729
+ occurrences += alt.occurences;
730
+ });
731
+ emptyAltCount.intersections -= occurrences;
732
+ return data;
733
+ };
734
+ const recordIncrease = (acc, base, field, isBlock, name2 = null) => {
735
+ const { words, length, emptyAlts } = increaseCounter(
736
+ base,
737
+ field,
738
+ isBlock,
739
+ name2
740
+ );
741
+ acc.wordCount += length;
742
+ acc.targetAlts.push(emptyAlts);
743
+ acc.keywordsDensity = buildKeywordDensityObject(
744
+ acc.keywords,
745
+ words,
746
+ acc.keywordsDensity
747
+ );
748
+ };
749
+ const processComponentFieldItem = (acc, item, fallbackBase, field, isBlock, name2) => {
750
+ if (Array.isArray(item)) {
751
+ item.forEach((entry) => {
752
+ if (entry && typeof entry === "object") {
753
+ recordIncrease(
754
+ acc,
755
+ entry,
756
+ field,
757
+ isBlock,
758
+ name2
759
+ );
760
+ }
761
+ });
762
+ } else {
763
+ recordIncrease(acc, fallbackBase, field, isBlock, name2);
764
+ }
765
+ };
766
+ const processSingleFieldDescriptor = (acc, modifiedData, data, isBlock) => {
767
+ if (data.field === null) {
768
+ recordIncrease(acc, modifiedData, data.name, isBlock);
769
+ return;
770
+ }
771
+ if (!data.inDz) {
772
+ const compKey = data.name.split(".").pop() || "";
773
+ const item = modifiedData?.[compKey] ?? modifiedData?.[data.name];
774
+ if (item && typeof item === "object") {
775
+ processComponentFieldItem(
776
+ acc,
777
+ item,
778
+ item,
779
+ data.field,
780
+ isBlock,
781
+ data.name
782
+ );
783
+ }
784
+ return;
785
+ }
786
+ const rawDz = modifiedData?.[data.inDz];
787
+ const dzComponents = Array.isArray(rawDz) ? rawDz.filter(
788
+ (x) => Boolean(x) && typeof x === "object"
789
+ ) : [];
790
+ if (dzComponents.length === 0) return;
791
+ const matchingComponents = dzComponents.filter(
792
+ (x) => x.__component === data.name
793
+ );
794
+ matchingComponents.forEach((component) => {
795
+ const item = component?.[data.field] ?? [];
796
+ processComponentFieldItem(
797
+ acc,
798
+ item,
799
+ component,
800
+ data.field,
801
+ isBlock,
802
+ data.name
803
+ );
804
+ });
805
+ };
806
+ const getRichTextData = (modifiedData, components, contentType) => {
807
+ const { richTextFields, blockFields } = getRichTextAndBlocksFields(
808
+ contentType,
809
+ components,
810
+ modifiedData
811
+ );
812
+ const { intersections, altTexts } = getRegularImageAltTexts(
813
+ contentType,
814
+ modifiedData
815
+ );
816
+ const emptyAltCount = {
817
+ intersections,
818
+ richTextAlts: [],
819
+ blockTextAlts: [],
820
+ altTexts
821
+ };
822
+ const seoKeywords = modifiedData?.seo?.keywords ?? "";
823
+ const keywords = typeof seoKeywords === "string" && seoKeywords.trim().length > 0 ? seoKeywords.toLowerCase().split(",").map((k) => k.trim()).filter((k) => k.length > 0) : [];
824
+ const initialKeywordsDensity = {};
825
+ keywords.forEach((keyword) => {
826
+ initialKeywordsDensity[keyword] = { count: 0 };
827
+ });
828
+ const richTextAcc = {
829
+ wordCount: 0,
830
+ keywords,
831
+ keywordsDensity: initialKeywordsDensity,
832
+ targetAlts: emptyAltCount.richTextAlts
833
+ };
834
+ richTextFields.forEach((data) => {
835
+ processSingleFieldDescriptor(richTextAcc, modifiedData, data, false);
836
+ });
837
+ const blockAcc = {
838
+ wordCount: richTextAcc.wordCount,
839
+ keywords,
840
+ keywordsDensity: richTextAcc.keywordsDensity,
841
+ targetAlts: emptyAltCount.blockTextAlts
842
+ };
843
+ blockFields.forEach((data) => {
844
+ processSingleFieldDescriptor(blockAcc, modifiedData, data, true);
845
+ });
846
+ return adjustIntersections({
847
+ wordCount: blockAcc.wordCount,
848
+ keywordsDensity: blockAcc.keywordsDensity,
849
+ emptyAltCount
850
+ });
851
+ };
852
+ const BG_PATH = [
853
+ "M134.933 17.417C137.768 17.417 140.067 19.7153 140.067 22.5503",
854
+ "C140.067 25.3854 137.768 27.6837 134.933 27.6837H105.6",
855
+ "C108.435 27.6837 110.733 29.9819 110.733 32.817",
856
+ "C110.733 35.6521 108.435 37.9503 105.6 37.9503H121.733",
857
+ "C124.568 37.9503 126.867 40.2486 126.867 43.0837",
858
+ "C126.867 45.9187 124.568 48.217 121.733 48.217H114.272",
859
+ "C110.698 48.217 107.8 50.5153 107.8 53.3503",
860
+ "C107.8 55.2404 109.267 56.9515 112.2 58.4837",
861
+ "C115.035 58.4837 117.333 60.7819 117.333 63.617",
862
+ "C117.333 66.4521 115.035 68.7503 112.2 68.7503H51.3333",
863
+ "C48.4982 68.7503 46.2 66.4521 46.2 63.617",
864
+ "C46.2 60.7819 48.4982 58.4837 51.3333 58.4837H22.7333",
865
+ "C19.8982 58.4837 17.6 56.1854 17.6 53.3503",
866
+ "C17.6 50.5153 19.8982 48.217 22.7333 48.217H52.0666",
867
+ "C54.9017 48.217 57.2 45.9187 57.2 43.0837",
868
+ "C57.2 40.2486 54.9017 37.9503 52.0666 37.9503H33.7333",
869
+ "C30.8982 37.9503 28.6 35.6521 28.6 32.817",
870
+ "C28.6 29.9819 30.8982 27.6837 33.7333 27.6837H63.0666",
871
+ "C60.2316 27.6837 57.9333 25.3854 57.9333 22.5503",
872
+ "C57.9333 19.7153 60.2316 17.417 63.0666 17.417H134.933Z",
873
+ "M134.933 37.9503C137.768 37.9503 140.067 40.2486 140.067 43.0837",
874
+ "C140.067 45.9187 137.768 48.217 134.933 48.217",
875
+ "C132.098 48.217 129.8 45.9187 129.8 43.0837",
876
+ "C129.8 40.2486 132.098 37.9503 134.933 37.9503Z"
877
+ ].join("");
878
+ const CARD_FILL_PATH = [
879
+ "M95.826 16.6834L102.647 66.4348L103.26 71.4261",
880
+ "C103.458 73.034 102.314 74.4976 100.706 74.695L57.7621 79.9679",
881
+ "C56.1542 80.1653 54.6906 79.0219 54.4932 77.4139L47.8816 23.5671",
882
+ "C47.7829 22.7631 48.3546 22.0313 49.1586 21.9326",
883
+ "C49.1637 21.932 49.1688 21.9313 49.1739 21.9307L52.7367 21.5311",
884
+ "L95.826 16.6834ZM55.6176 21.208L58.9814 20.8306Z"
885
+ ].join("");
886
+ const CARD_STROKE_PATH = [
887
+ "M55.6176 21.208L58.9814 20.8306M95.826 16.6834L102.647 66.4348",
888
+ "L103.26 71.4261C103.458 73.034 102.314 74.4976 100.706 74.695",
889
+ "L57.7621 79.9679C56.1542 80.1653 54.6906 79.0219 54.4932 77.4139",
890
+ "L47.8816 23.5671C47.7829 22.7631 48.3546 22.0313 49.1586 21.9326",
891
+ "C49.1637 21.932 49.1688 21.9313 49.1739 21.9307L52.7367 21.5311",
892
+ "L95.826 16.6834Z"
893
+ ].join("");
894
+ const INNER_CARD_PATH = [
895
+ "M93.9695 19.8144L100.144 64.9025L100.699 69.4258",
896
+ "C100.878 70.8831 99.8559 72.2077 98.416 72.3845L59.9585 77.1065",
897
+ "C58.5185 77.2833 57.2062 76.2453 57.0272 74.7881L51.0506 26.112",
898
+ "C50.9519 25.308 51.5236 24.5762 52.3276 24.4775L57.0851 23.8934"
899
+ ].join("");
900
+ const DOC_PATH = [
901
+ "M97.701 7.33301H64.2927C63.7358 7.33301 63.2316 7.55873 62.8667 7.92368",
902
+ "C62.5017 8.28862 62.276 8.79279 62.276 9.34967V65.083",
903
+ "C62.276 65.6399 62.5017 66.1441 62.8667 66.509",
904
+ "C63.2316 66.874 63.7358 67.0997 64.2927 67.0997H107.559",
905
+ "C108.116 67.0997 108.62 66.874 108.985 66.509",
906
+ "C109.35 66.1441 109.576 65.6399 109.576 65.083V19.202",
907
+ "C109.576 18.6669 109.363 18.1537 108.985 17.7755L99.1265 7.92324",
908
+ "C98.7484 7.54531 98.2356 7.33301 97.701 7.33301Z"
909
+ ].join("");
910
+ const FOLD_PATH = "M98.026 8.17871V16.6833C98.026 17.8983 99.011 18.8833 100.226 18.8833H106.044";
911
+ const LINES_PATH = [
912
+ "M70.1594 56.2838H89.2261M70.1594 18.8838H89.2261H70.1594Z",
913
+ "M70.1594 27.6838H101.693H70.1594ZM70.1594 37.2171H101.693H70.1594Z",
914
+ "M70.1594 46.7505H101.693H70.1594Z"
915
+ ].join("");
916
+ const Illo = () => /* @__PURE__ */ jsxRuntime.jsxs(
917
+ "svg",
918
+ {
919
+ width: "159",
920
+ height: "88",
921
+ viewBox: "0 0 159 88",
922
+ fill: "none",
923
+ xmlns: "http://www.w3.org/2000/svg",
924
+ children: [
925
+ /* @__PURE__ */ jsxRuntime.jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: BG_PATH, fill: "#DBDBFA" }),
926
+ /* @__PURE__ */ jsxRuntime.jsx(
927
+ "path",
928
+ {
929
+ fillRule: "evenodd",
930
+ clipRule: "evenodd",
931
+ d: CARD_FILL_PATH,
932
+ fill: "white"
933
+ }
934
+ ),
935
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: CARD_STROKE_PATH, stroke: "#7E7BF6", strokeWidth: "2.5" }),
936
+ /* @__PURE__ */ jsxRuntime.jsx(
937
+ "path",
938
+ {
939
+ fillRule: "evenodd",
940
+ clipRule: "evenodd",
941
+ d: INNER_CARD_PATH,
942
+ fill: "#F0F0FF"
943
+ }
944
+ ),
945
+ /* @__PURE__ */ jsxRuntime.jsx(
946
+ "path",
947
+ {
948
+ fillRule: "evenodd",
949
+ clipRule: "evenodd",
950
+ d: DOC_PATH,
951
+ fill: "white",
952
+ stroke: "#7F7CFA",
953
+ strokeWidth: "2.5"
954
+ }
955
+ ),
956
+ /* @__PURE__ */ jsxRuntime.jsx(
957
+ "path",
958
+ {
959
+ d: FOLD_PATH,
960
+ stroke: "#807EFA",
961
+ strokeWidth: "2.5",
962
+ strokeLinecap: "round",
963
+ strokeLinejoin: "round"
964
+ }
965
+ ),
966
+ /* @__PURE__ */ jsxRuntime.jsx(
967
+ "path",
968
+ {
969
+ d: LINES_PATH,
970
+ stroke: "#817FFA",
971
+ strokeWidth: "2.5",
972
+ strokeLinecap: "round",
973
+ strokeLinejoin: "round"
974
+ }
975
+ )
976
+ ]
977
+ }
978
+ );
979
+ const SEOAccordion = ({
980
+ title,
981
+ status,
982
+ component,
983
+ label
984
+ }) => {
985
+ const getIcon = () => {
986
+ switch (status?.qualityVerdict) {
987
+ case qualityVerdict.good:
988
+ return icons.CheckCircle;
989
+ case qualityVerdict.improvements:
990
+ return icons.WarningCircle;
991
+ case qualityVerdict.bad:
992
+ return icons.CrossCircle;
993
+ default:
994
+ return icons.WarningCircle;
995
+ }
996
+ };
997
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Root, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Item, { value: "acc-01", children: [
998
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Header, { children: [
999
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Trigger, { icon: getIcon(), children: title }),
1000
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Actions, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.IconButton, { label, children: /* @__PURE__ */ jsxRuntime.jsx(icons.Information, {}) }) })
1001
+ ] }),
1002
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Content, { children: [
1003
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, paddingLeft: 4, paddingBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: status?.message }) }),
1004
+ component
1005
+ ] })
1006
+ ] }) });
1007
+ };
1008
+ const AlternativeTextCheck = ({
1009
+ intersections,
1010
+ richTextAlts,
1011
+ altTexts,
1012
+ checks
1013
+ }) => {
1014
+ const { formatMessage } = reactIntl.useIntl();
1015
+ const dispatch = React__namespace.useContext(SeoCheckerContext);
1016
+ React__namespace.useEffect(() => {
1017
+ let status = {
1018
+ message: formatMessage({
1019
+ id: getTrad("SEOChecks.alternativeTextCheck.default"),
1020
+ defaultMessage: "All your images contain an alt attribute! Congrats!"
1021
+ }),
1022
+ qualityVerdict: qualityVerdict.good
1023
+ };
1024
+ const missingRichTextAlt = richTextAlts.filter(
1025
+ (x) => x.occurences !== 0
1026
+ ).length;
1027
+ if (intersections === 0) {
1028
+ status = {
1029
+ message: formatMessage({
1030
+ id: getTrad("SEOChecks.alternativeTextCheck.intersection-zero"),
1031
+ defaultMessage: "The name and the alternative text of your images are all the same. Strapi automatically generate the alt attribute from the name by default if the field was empty during the media upload. Having alt attribute clearly describing images is a very good practice."
1032
+ }),
1033
+ qualityVerdict: qualityVerdict.bad
1034
+ };
1035
+ } else if (altTexts.includes("")) {
1036
+ const count = Math.abs(intersections ?? 0);
1037
+ status = {
1038
+ message: `${count} ${formatMessage({
1039
+ id: getTrad("SEOChecks.alternativeTextCheck.intersection-negative"),
1040
+ defaultMessage: "Some images from a media field are missing an alt attribute."
1041
+ })}`,
1042
+ qualityVerdict: qualityVerdict.improvements
1043
+ };
1044
+ } else if (missingRichTextAlt >= 1) {
1045
+ status = {
1046
+ message: formatMessage({
1047
+ id: getTrad("SEOChecks.alternativeTextCheck.richtext-missing-one"),
1048
+ defaultMessage: "At least one image in any 1st level richtext editor is missing an alt attribute."
1049
+ }),
1050
+ qualityVerdict: qualityVerdict.improvements
1051
+ };
1052
+ }
1053
+ if (!isSameCheckStatus(status, checks.alternativeText) && dispatch) {
1054
+ dispatch({
1055
+ type: "UPDATE_PONCTUAL",
1056
+ value: { ...status, entity: "alternativeText" }
1057
+ });
1058
+ }
1059
+ }, [intersections, richTextAlts, altTexts]);
1060
+ return /* @__PURE__ */ jsxRuntime.jsx(
1061
+ SEOAccordion,
1062
+ {
1063
+ title: formatMessage({
1064
+ id: getTrad("SEOModal.summary-title.alt-attributes"),
1065
+ defaultMessage: "Alternative Text"
1066
+ }),
1067
+ status: checks.alternativeText,
1068
+ label: formatMessage({
1069
+ id: getTrad("SEOChecks.alternativeTextCheck.label"),
1070
+ defaultMessage: "This will check if you have any missing alternative text for your images (media fields) and in your 1st level richtext editors."
1071
+ }),
1072
+ component: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 2, background: "neutral100", children: [
1073
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { spacing: 2, horizontal: true, padding: 2, children: [
1074
+ /* @__PURE__ */ jsxRuntime.jsx(icons.WarningCircle, { "aria-hidden": true, fill: "warning600" }),
1075
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { paddingLeft: 1, children: [
1076
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { fontWeight: "bold", children: [
1077
+ altTexts.filter((item) => item === "").length,
1078
+ " "
1079
+ ] }),
1080
+ formatMessage({
1081
+ id: getTrad("SEOChecks.alternativeTextCheck.missing-image"),
1082
+ defaultMessage: "missing on first level image fields or inside a component."
1083
+ })
1084
+ ] })
1085
+ ] }),
1086
+ richTextAlts.map((alt, index2) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { spacing: 2, horizontal: true, padding: 2, children: alt.occurences > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1087
+ /* @__PURE__ */ jsxRuntime.jsx(icons.WarningCircle, { "aria-hidden": true, fill: "warning600" }),
1088
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { paddingLeft: 1, children: [
1089
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { fontWeight: "bold", children: [
1090
+ alt.occurences,
1091
+ " "
1092
+ ] }),
1093
+ formatMessage({
1094
+ id: getTrad(
1095
+ "SEOChecks.alternativeTextCheck.missing-text"
1096
+ ),
1097
+ defaultMessage: "missing alt in the following richtext field:"
1098
+ }),
1099
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { fontWeight: "bold", children: [
1100
+ " ",
1101
+ alt.field
1102
+ ] })
1103
+ ] })
1104
+ ] }) }, index2)),
1105
+ /* @__PURE__ */ jsxRuntime.jsx(
1106
+ designSystem.Flex,
1107
+ {
1108
+ spacing: 2,
1109
+ paddingTop: 4,
1110
+ paddingLeft: 2,
1111
+ paddingRight: 2,
1112
+ paddingBottom: 4,
1113
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Status, { variant: "secondary", showBullet: false, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { children: [
1114
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { fontWeight: "bold", children: [
1115
+ formatMessage({
1116
+ id: getTrad("SEOChecks.alternativeTextCheck.tip-label"),
1117
+ defaultMessage: "Tip:"
1118
+ }),
1119
+ " "
1120
+ ] }),
1121
+ formatMessage({
1122
+ id: getTrad("SEOChecks.alternativeTextCheck.tip-before"),
1123
+ defaultMessage: "Implement a rule in your front-end code that uses the name of your images if the"
1124
+ }),
1125
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: " alternativeText " }),
1126
+ formatMessage({
1127
+ id: getTrad("SEOChecks.alternativeTextCheck.tip-after"),
1128
+ defaultMessage: "field is empty to further reduce the risk of an empty alt attribute on your website."
1129
+ })
1130
+ ] }) })
1131
+ }
1132
+ )
1133
+ ] })
1134
+ }
1135
+ );
1136
+ };
1137
+ const CanonicalUrlCheck = ({
1138
+ canonicalUrl,
1139
+ checks
1140
+ }) => {
1141
+ const { formatMessage } = reactIntl.useIntl();
1142
+ const dispatch = React__namespace.useContext(SeoCheckerContext);
1143
+ React__namespace.useEffect(() => {
1144
+ let status = {
1145
+ message: formatMessage({
1146
+ id: getTrad("SEOChecks.canonicalUrlCheck.found"),
1147
+ defaultMessage: "A canonical URL has been found."
1148
+ }),
1149
+ qualityVerdict: qualityVerdict.good
1150
+ };
1151
+ if (typeof canonicalUrl !== "string" || isEmptyValue(canonicalUrl)) {
1152
+ status = {
1153
+ message: formatMessage({
1154
+ id: getTrad("SEOChecks.canonicalUrlCheck.default"),
1155
+ defaultMessage: "No Canonical URL has been found."
1156
+ }),
1157
+ qualityVerdict: qualityVerdict.bad
1158
+ };
1159
+ }
1160
+ if (!isSameCheckStatus(status, checks.canonicalUrl) && dispatch) {
1161
+ dispatch({
1162
+ type: "UPDATE_PONCTUAL",
1163
+ value: { ...status, entity: "canonicalUrl" }
1164
+ });
1165
+ }
1166
+ }, [canonicalUrl]);
1167
+ return /* @__PURE__ */ jsxRuntime.jsx(
1168
+ SEOAccordion,
1169
+ {
1170
+ title: formatMessage({
1171
+ id: getTrad("SEOModal.summary-title.canonical-url"),
1172
+ defaultMessage: "Canonical URL"
1173
+ }),
1174
+ status: checks.canonicalUrl,
1175
+ label: formatMessage({
1176
+ id: getTrad("SEOChecks.canonicalUrlCheck.label"),
1177
+ defaultMessage: "This will check if you have a canonical URL."
1178
+ }),
1179
+ component: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: canonicalUrl ? 4 : 0, background: "neutral100", children: canonicalUrl && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: canonicalUrl }) })
1180
+ }
1181
+ );
1182
+ };
1183
+ const KeywordDensityCheck = ({
1184
+ keywordsDensity,
1185
+ checks
1186
+ }) => {
1187
+ const { formatMessage } = reactIntl.useIntl();
1188
+ const dispatch = React__namespace.useContext(SeoCheckerContext);
1189
+ React__namespace.useEffect(() => {
1190
+ let status = {
1191
+ message: formatMessage({
1192
+ id: getTrad("SEOChecks.keywordsDensityCheck.default"),
1193
+ defaultMessage: "Every keywords are used more than 2 times."
1194
+ }),
1195
+ qualityVerdict: qualityVerdict.good
1196
+ };
1197
+ if (isEmptyValue(keywordsDensity)) {
1198
+ status = {
1199
+ message: formatMessage({
1200
+ id: getTrad("SEOChecks.keywordsDensityCheck.no-keywords"),
1201
+ defaultMessage: "No keywords were found in your SEO component."
1202
+ }),
1203
+ qualityVerdict: qualityVerdict.improvements
1204
+ };
1205
+ } else {
1206
+ let hasUnused = false;
1207
+ let hasUsedOnce = false;
1208
+ Object.keys(keywordsDensity).forEach((keyword) => {
1209
+ const count = keywordsDensity[keyword]?.count ?? 0;
1210
+ if (count === 0) {
1211
+ hasUnused = true;
1212
+ } else if (count <= 1) {
1213
+ hasUsedOnce = true;
1214
+ }
1215
+ });
1216
+ if (hasUsedOnce) {
1217
+ status = {
1218
+ message: formatMessage({
1219
+ id: getTrad("SEOChecks.keywordsDensityCheck.one-used-once"),
1220
+ defaultMessage: "One keyword is only used once in your content."
1221
+ }),
1222
+ qualityVerdict: qualityVerdict.bad
1223
+ };
1224
+ } else if (hasUnused) {
1225
+ status = {
1226
+ message: formatMessage({
1227
+ id: getTrad("SEOChecks.keywordsDensityCheck.one-not-used"),
1228
+ defaultMessage: "One keyword is not being used in your content."
1229
+ }),
1230
+ qualityVerdict: qualityVerdict.improvements
1231
+ };
1232
+ }
1233
+ }
1234
+ if (!isSameCheckStatus(status, checks.keywordsDensity) && dispatch) {
1235
+ dispatch({
1236
+ type: "UPDATE_PONCTUAL",
1237
+ value: { ...status, entity: "keywordsDensity" }
1238
+ });
1239
+ }
1240
+ }, [keywordsDensity]);
1241
+ return /* @__PURE__ */ jsxRuntime.jsx(
1242
+ SEOAccordion,
1243
+ {
1244
+ title: formatMessage({
1245
+ id: getTrad("SEOModal.summary-title.word-densisty"),
1246
+ defaultMessage: "Keyword Density"
1247
+ }),
1248
+ status: checks.keywordsDensity,
1249
+ label: formatMessage({
1250
+ id: getTrad("SEOChecks.keywordsDensityCheck.label"),
1251
+ defaultMessage: "Define keywords you want to rank for in your SEO component. This will analyse the density of your keywords in your 1st level richtext editors."
1252
+ }),
1253
+ component: keywordsDensity && !isEmptyValue(keywordsDensity) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 2, background: "neutral100", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { wrap: "wrap", children: Object.keys(keywordsDensity).map((keyword) => {
1254
+ const count = (keywordsDensity[keyword]?.count ?? 0).toString();
1255
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: `${keyword}: ${count}` }) }, keyword);
1256
+ }) }) })
1257
+ }
1258
+ );
1259
+ };
1260
+ const LastUpdatedAtCheck = ({
1261
+ updatedAt,
1262
+ checks
1263
+ }) => {
1264
+ const { formatMessage } = reactIntl.useIntl();
1265
+ const dispatch = React__namespace.useContext(SeoCheckerContext);
1266
+ const parsedTimestamp = typeof updatedAt === "string" && !isEmptyValue(updatedAt) ? Date.parse(updatedAt) : Number.NaN;
1267
+ const isValidUpdatedAt = !Number.isNaN(parsedTimestamp);
1268
+ React__namespace.useEffect(() => {
1269
+ let status = {
1270
+ message: formatMessage({
1271
+ id: getTrad("SEOChecks.lastUpdatedAtCheck.default"),
1272
+ defaultMessage: "This content was modified over a year ago! Search engines love fresh content."
1273
+ }),
1274
+ qualityVerdict: qualityVerdict.improvements
1275
+ };
1276
+ if (!isValidUpdatedAt) {
1277
+ status = {
1278
+ message: formatMessage({
1279
+ id: getTrad("SEOChecks.lastUpdatedAtCheck.save-content"),
1280
+ defaultMessage: "You must save this entry first."
1281
+ }),
1282
+ qualityVerdict: qualityVerdict.bad
1283
+ };
1284
+ } else {
1285
+ const oneYearAgoDate = /* @__PURE__ */ new Date();
1286
+ oneYearAgoDate.setFullYear(oneYearAgoDate.getFullYear() - 1);
1287
+ if (parsedTimestamp >= oneYearAgoDate.getTime()) {
1288
+ status = {
1289
+ message: formatMessage({
1290
+ id: getTrad("SEOChecks.lastUpdatedAtCheck.success"),
1291
+ defaultMessage: "Awesome! This content was last modified in less than an year ago!"
1292
+ }),
1293
+ qualityVerdict: qualityVerdict.good
1294
+ };
1295
+ }
1296
+ }
1297
+ if (!isSameCheckStatus(status, checks.lastUpdatedAt) && dispatch) {
1298
+ dispatch({
1299
+ type: "UPDATE_PONCTUAL",
1300
+ value: { ...status, entity: "lastUpdatedAt" }
1301
+ });
1302
+ }
1303
+ }, [updatedAt, isValidUpdatedAt, parsedTimestamp]);
1304
+ return /* @__PURE__ */ jsxRuntime.jsx(
1305
+ SEOAccordion,
1306
+ {
1307
+ title: formatMessage({
1308
+ id: getTrad("SEOModal.summary-title.last-updated-at"),
1309
+ defaultMessage: "Last updated at"
1310
+ }),
1311
+ label: formatMessage({
1312
+ id: getTrad("SEOChecks.lastUpdatedAtCheck.label"),
1313
+ defaultMessage: "Search engines love fresh content. This will check if your entry was last modified in less than an year ago."
1314
+ }),
1315
+ status: checks.lastUpdatedAt,
1316
+ component: isValidUpdatedAt && updatedAt && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral100", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "omega", children: [
1317
+ formatMessage({
1318
+ id: getTrad("SEOChecks.lastUpdatedAtCheck.last"),
1319
+ defaultMessage: "Last updated at:"
1320
+ }),
1321
+ " ",
1322
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: [
1323
+ " ",
1324
+ dateFns.formatDistance(new Date(updatedAt), /* @__PURE__ */ new Date(), {
1325
+ addSuffix: true
1326
+ })
1327
+ ] })
1328
+ ] }) })
1329
+ }
1330
+ );
1331
+ };
1332
+ const MetaDescriptionCheck = ({
1333
+ metaDescription,
1334
+ checks
1335
+ }) => {
1336
+ const { formatMessage } = reactIntl.useIntl();
1337
+ const dispatch = React__namespace.useContext(SeoCheckerContext);
1338
+ const maxLength = 160;
1339
+ const minimumLength = 50;
1340
+ React__namespace.useEffect(() => {
1341
+ let status = {
1342
+ message: formatMessage({
1343
+ id: getTrad("SEOChecks.metaDescriptionCheck.default"),
1344
+ defaultMessage: "A Meta Description has been found!"
1345
+ }),
1346
+ qualityVerdict: qualityVerdict.good
1347
+ };
1348
+ if (typeof metaDescription !== "string" || isEmptyValue(metaDescription)) {
1349
+ status = {
1350
+ message: formatMessage({
1351
+ id: getTrad("SEOChecks.metaDescriptionCheck.not-found"),
1352
+ defaultMessage: "No Meta Description has been found."
1353
+ }),
1354
+ qualityVerdict: qualityVerdict.improvements
1355
+ };
1356
+ } else if (metaDescription.length > maxLength) {
1357
+ status = {
1358
+ message: formatMessage({
1359
+ id: getTrad("Title-settings.metaDescription-too-long"),
1360
+ defaultMessage: "Meta Description is too long"
1361
+ }),
1362
+ qualityVerdict: qualityVerdict.bad
1363
+ };
1364
+ } else if (metaDescription.length < minimumLength) {
1365
+ status = {
1366
+ message: formatMessage({
1367
+ id: getTrad("Title-settings.metaDescription-too-short"),
1368
+ defaultMessage: "Meta Description is too short"
1369
+ }),
1370
+ qualityVerdict: qualityVerdict.bad
1371
+ };
1372
+ }
1373
+ if (!isSameCheckStatus(status, checks.metaDescription) && dispatch) {
1374
+ dispatch({
1375
+ type: "UPDATE_PONCTUAL",
1376
+ value: { ...status, entity: "metaDescription" }
1377
+ });
1378
+ }
1379
+ }, [metaDescription]);
1380
+ return /* @__PURE__ */ jsxRuntime.jsx(
1381
+ SEOAccordion,
1382
+ {
1383
+ title: formatMessage({
1384
+ id: getTrad("SEOModal.summary-title.meta-description"),
1385
+ defaultMessage: "Meta description"
1386
+ }),
1387
+ status: checks.metaDescription,
1388
+ label: formatMessage({
1389
+ id: getTrad("Title-settings.metaDescription-tooltip"),
1390
+ defaultMessage: `A meta description is an HTML tag used to describe the content of a web page.
1391
+ This description appears below the title and URL of your page as it appears in search engine results.
1392
+ For it to remain visible in Google, it must not exceed 140-${maxLength} characters.`
1393
+ }),
1394
+ component: metaDescription && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 4, background: "neutral100", children: [
1395
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: metaDescription }),
1396
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { horizontal: true, spacing: 2, children: [
1397
+ /* @__PURE__ */ jsxRuntime.jsx(
1398
+ designSystem.ProgressBar,
1399
+ {
1400
+ value: metaDescription.length * 100 / maxLength > 100 ? 100 : metaDescription.length * 100 / maxLength
1401
+ }
1402
+ ),
1403
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", padding: 2, children: [
1404
+ "(",
1405
+ metaDescription.length,
1406
+ "/",
1407
+ maxLength,
1408
+ ")"
1409
+ ] })
1410
+ ] }) })
1411
+ ] })
1412
+ }
1413
+ );
1414
+ };
1415
+ const robotTags = [
1416
+ { name: "noindex", message: "Search engines will index this page." },
1417
+ {
1418
+ name: "nofollow",
1419
+ message: "Search engines will follow links from this page"
1420
+ },
1421
+ { name: "noarchive", message: "Search engines will cache your page." },
1422
+ {
1423
+ name: "nosnippet",
1424
+ message: "Search engines will show a snippet of this page in search results."
1425
+ },
1426
+ {
1427
+ name: "noimageindex",
1428
+ message: "Google will index the images on this page."
1429
+ },
1430
+ {
1431
+ name: "nositelinkssearchbox",
1432
+ message: "Google will show the search box in search results."
1433
+ }
1434
+ ];
1435
+ const MetaRobotCheck = ({ metaRobots, checks }) => {
1436
+ const { formatMessage } = reactIntl.useIntl();
1437
+ const dispatch = React__namespace.useContext(SeoCheckerContext);
1438
+ const [tags, setTags] = React__namespace.useState([]);
1439
+ React__namespace.useEffect(() => {
1440
+ let status = {
1441
+ message: formatMessage({
1442
+ id: getTrad("SEOChecks.metaRobotsCheck.default"),
1443
+ defaultMessage: "Robot meta tags have been found!"
1444
+ }),
1445
+ qualityVerdict: qualityVerdict.good
1446
+ };
1447
+ if (typeof metaRobots !== "string" || isEmptyValue(metaRobots)) {
1448
+ status = {
1449
+ message: formatMessage({
1450
+ id: getTrad("SEOChecks.metaRobotsCheck.not-found"),
1451
+ defaultMessage: "No Robot meta tags have been found."
1452
+ }),
1453
+ qualityVerdict: qualityVerdict.good
1454
+ };
1455
+ setTags([]);
1456
+ } else {
1457
+ setTags(metaRobots.split(","));
1458
+ }
1459
+ if (!isSameCheckStatus(status, checks.metaRobots) && dispatch) {
1460
+ dispatch({
1461
+ type: "UPDATE_PONCTUAL",
1462
+ value: { ...status, entity: "metaRobots" }
1463
+ });
1464
+ }
1465
+ }, [metaRobots]);
1466
+ return /* @__PURE__ */ jsxRuntime.jsx(
1467
+ SEOAccordion,
1468
+ {
1469
+ title: formatMessage({
1470
+ id: getTrad("SEOModal.summary-title.meta-robots"),
1471
+ defaultMessage: "Meta Robots"
1472
+ }),
1473
+ status: checks.metaRobots,
1474
+ label: formatMessage({
1475
+ id: getTrad("SEOChecks.metaRobotsCheck.label"),
1476
+ defaultMessage: "The robots meta tag informs search engines which pages on your site should be indexed and more."
1477
+ }),
1478
+ component: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 2, background: "neutral100", children: [
1479
+ robotTags.map((tag, index2) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { spacing: 2, horizontal: true, padding: 2, children: [
1480
+ tags.find((x) => x.trim() === tag.name) ? /* @__PURE__ */ jsxRuntime.jsx(icons.CheckCircle, { "aria-hidden": true, fill: "success600" }) : /* @__PURE__ */ jsxRuntime.jsx(icons.WarningCircle, { "aria-hidden": true, fill: "warning600" }),
1481
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { paddingLeft: 1, children: tags.find((x) => x.trim() === tag.name) ? formatMessage(
1482
+ {
1483
+ id: getTrad("SEOChecks.metaRobotsCheck.activated"),
1484
+ defaultMessage: "{tag} is activated: {message}"
1485
+ },
1486
+ {
1487
+ tag: tag.name,
1488
+ message: tag.message.replace("will", "will not")
1489
+ }
1490
+ ) : formatMessage(
1491
+ {
1492
+ id: getTrad("SEOChecks.metaRobotsCheck.disabled"),
1493
+ defaultMessage: "{tag} is disabled: {message}"
1494
+ },
1495
+ {
1496
+ tag: tag.name,
1497
+ message: tag.message
1498
+ }
1499
+ ) })
1500
+ ] }, index2)),
1501
+ /* @__PURE__ */ jsxRuntime.jsx(
1502
+ designSystem.Flex,
1503
+ {
1504
+ spacing: 2,
1505
+ paddingTop: 4,
1506
+ paddingLeft: 2,
1507
+ paddingRight: 2,
1508
+ paddingBottom: 4,
1509
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Status, { variant: "secondary", showBullet: false, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { children: [
1510
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { fontWeight: "bold", children: [
1511
+ formatMessage({
1512
+ id: getTrad("SEOChecks.metaRobotsCheck.notice-label"),
1513
+ defaultMessage: "Notice:"
1514
+ }),
1515
+ " "
1516
+ ] }),
1517
+ formatMessage({
1518
+ id: getTrad("SEOChecks.metaRobotsCheck.notice-before"),
1519
+ defaultMessage: "In order to not index your entry and no follow, your MetaRobots field should contain the following:"
1520
+ }),
1521
+ " ",
1522
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: formatMessage({
1523
+ id: getTrad("SEOChecks.metaRobotsCheck.notice-value"),
1524
+ defaultMessage: "noindex, nofollow"
1525
+ }) }),
1526
+ formatMessage({
1527
+ id: getTrad("SEOChecks.metaRobotsCheck.notice-after"),
1528
+ defaultMessage: '. The rest should be handled by your front-end code logic. "If the field contains noindex, then you need to create the corresponding meta tag etc..."'
1529
+ })
1530
+ ] }) })
1531
+ }
1532
+ )
1533
+ ] })
1534
+ }
1535
+ );
1536
+ };
1537
+ const MetaTitleCheck = ({ metaTitle, checks }) => {
1538
+ const { formatMessage } = reactIntl.useIntl();
1539
+ const dispatch = React__namespace.useContext(SeoCheckerContext);
1540
+ const maxLength = 60;
1541
+ React__namespace.useEffect(() => {
1542
+ let status = {
1543
+ message: formatMessage({
1544
+ id: getTrad("SEOChecks.metaTitleCheck.default"),
1545
+ defaultMessage: "A Meta Title has been found!"
1546
+ }),
1547
+ qualityVerdict: qualityVerdict.good
1548
+ };
1549
+ if (typeof metaTitle !== "string" || isEmptyValue(metaTitle)) {
1550
+ status = {
1551
+ message: formatMessage({
1552
+ id: getTrad("SEOChecks.metaTitleCheck.not-found"),
1553
+ defaultMessage: "No Meta Title has been found."
1554
+ }),
1555
+ qualityVerdict: qualityVerdict.improvements
1556
+ };
1557
+ } else if (metaTitle.length > maxLength) {
1558
+ status = {
1559
+ message: formatMessage({
1560
+ id: getTrad("Title-settings.metaTitle-too-long"),
1561
+ defaultMessage: "Meta Title is too long"
1562
+ }),
1563
+ qualityVerdict: qualityVerdict.bad
1564
+ };
1565
+ }
1566
+ if (!isSameCheckStatus(status, checks.metaTitle) && dispatch) {
1567
+ dispatch({
1568
+ type: "UPDATE_PONCTUAL",
1569
+ value: { ...status, entity: "metaTitle" }
1570
+ });
1571
+ }
1572
+ }, [metaTitle]);
1573
+ return /* @__PURE__ */ jsxRuntime.jsx(
1574
+ SEOAccordion,
1575
+ {
1576
+ title: formatMessage({
1577
+ id: getTrad("SEOModal.summary-title.meta-title"),
1578
+ defaultMessage: "Meta title"
1579
+ }),
1580
+ status: checks.metaTitle,
1581
+ label: formatMessage({
1582
+ id: getTrad("Title-settings.metaTitle-tooltip"),
1583
+ defaultMessage: "The title tag is the clickable title of a webpage that appears with the result on the SERP (search engine page results page).\n You should aim to make your SEO titles around 60 characters long. Clear title tags will go a long way towards making your website easy to read and understand."
1584
+ }),
1585
+ component: metaTitle && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 4, background: "neutral100", children: [
1586
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: metaTitle }),
1587
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { horizontal: true, spacing: 2, children: [
1588
+ /* @__PURE__ */ jsxRuntime.jsx(
1589
+ designSystem.ProgressBar,
1590
+ {
1591
+ size: "M",
1592
+ value: metaTitle.length * 100 / maxLength > 100 ? 100 : metaTitle.length * 100 / maxLength
1593
+ }
1594
+ ),
1595
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", padding: 2, children: [
1596
+ "(",
1597
+ metaTitle.length,
1598
+ "/",
1599
+ maxLength,
1600
+ ")"
1601
+ ] })
1602
+ ] }) })
1603
+ ] })
1604
+ }
1605
+ );
1606
+ };
1607
+ const OpenGraphCheck = ({ openGraph, checks }) => {
1608
+ const { formatMessage } = reactIntl.useIntl();
1609
+ const dispatch = React__namespace.useContext(SeoCheckerContext);
1610
+ React__namespace.useEffect(() => {
1611
+ let status;
1612
+ if (!openGraph || typeof openGraph !== "object") {
1613
+ status = {
1614
+ message: formatMessage({
1615
+ id: getTrad("SEOChecks.openGraphCheck.not-found"),
1616
+ defaultMessage: "No OpenGraph tags have been found."
1617
+ }),
1618
+ qualityVerdict: qualityVerdict.bad
1619
+ };
1620
+ } else if (isEmptyValue(openGraph.ogTitle) || isEmptyValue(openGraph.ogDescription) || !openGraph.ogImage) {
1621
+ status = {
1622
+ message: formatMessage({
1623
+ id: getTrad("SEOChecks.openGraphCheck.not-configured"),
1624
+ defaultMessage: "OpenGraph minimum required tags are not configured."
1625
+ }),
1626
+ qualityVerdict: qualityVerdict.improvements
1627
+ };
1628
+ } else {
1629
+ status = {
1630
+ message: formatMessage({
1631
+ id: getTrad("SEOChecks.openGraphCheck.configured"),
1632
+ defaultMessage: "OpenGraph tags are configured"
1633
+ }),
1634
+ qualityVerdict: qualityVerdict.good
1635
+ };
1636
+ }
1637
+ if (!isSameCheckStatus(status, checks.openGraph) && dispatch) {
1638
+ dispatch({
1639
+ type: "UPDATE_PONCTUAL",
1640
+ value: { ...status, entity: "openGraph" }
1641
+ });
1642
+ }
1643
+ }, [openGraph]);
1644
+ return /* @__PURE__ */ jsxRuntime.jsx(
1645
+ SEOAccordion,
1646
+ {
1647
+ title: formatMessage({
1648
+ id: getTrad("SEOModal.summary-title.open-graph"),
1649
+ defaultMessage: "OpenGraph Tags"
1650
+ }),
1651
+ status: checks.openGraph,
1652
+ label: formatMessage({
1653
+ id: getTrad("SEOChecks.openGraphCheck.label"),
1654
+ defaultMessage: "OpenGraph tags allow you to manage the title, description & image of your posts."
1655
+ })
1656
+ }
1657
+ );
1658
+ };
1659
+ const StructuredDataCheck = ({
1660
+ structuredData,
1661
+ checks
1662
+ }) => {
1663
+ const { formatMessage } = reactIntl.useIntl();
1664
+ const dispatch = React__namespace.useContext(SeoCheckerContext);
1665
+ React__namespace.useEffect(() => {
1666
+ let status = {
1667
+ message: formatMessage({
1668
+ id: getTrad("SEOChecks.structuredDataCheck.default"),
1669
+ defaultMessage: "A Structured Data json has been found! However we can validate the accuracy of its content."
1670
+ }),
1671
+ qualityVerdict: qualityVerdict.good
1672
+ };
1673
+ if (isEmptyValue(structuredData)) {
1674
+ status = {
1675
+ message: formatMessage({
1676
+ id: getTrad("SEOChecks.structuredDataCheck.not-found"),
1677
+ defaultMessage: "No Structured Data json has been found."
1678
+ }),
1679
+ qualityVerdict: qualityVerdict.bad
1680
+ };
1681
+ }
1682
+ if (!isSameCheckStatus(status, checks.structuredData) && dispatch) {
1683
+ dispatch({
1684
+ type: "UPDATE_PONCTUAL",
1685
+ value: { ...status, entity: "structuredData" }
1686
+ });
1687
+ }
1688
+ }, [structuredData]);
1689
+ return /* @__PURE__ */ jsxRuntime.jsx(
1690
+ SEOAccordion,
1691
+ {
1692
+ title: formatMessage({
1693
+ id: getTrad("SEOModal.summary-title.structured-data"),
1694
+ defaultMessage: "JSON Structured Data"
1695
+ }),
1696
+ status: checks.structuredData,
1697
+ label: formatMessage({
1698
+ id: getTrad("SEOChecks.structuredDataCheck.label"),
1699
+ defaultMessage: "Structured data is a standardized format for providing information about a page and classifying the page content."
1700
+ })
1701
+ }
1702
+ );
1703
+ };
1704
+ const WordCountCheck = ({ wordCount, checks }) => {
1705
+ const { formatMessage } = reactIntl.useIntl();
1706
+ const dispatch = React__namespace.useContext(SeoCheckerContext);
1707
+ React__namespace.useEffect(() => {
1708
+ let status = {
1709
+ message: formatMessage({
1710
+ id: getTrad("SEOChecks.wordCountCheck.default"),
1711
+ defaultMessage: "You have more than 300 words. The word counter is not a direct ranking factor. But your content should be as high quality as possible, with relevant and unique information. To meet these conditions, your content requires a minimum of paragraphs, and therefore of words."
1712
+ }),
1713
+ qualityVerdict: qualityVerdict.good
1714
+ };
1715
+ if (typeof wordCount !== "number") {
1716
+ status = {
1717
+ message: formatMessage({
1718
+ id: getTrad("SEOChecks.wordCountCheck.not-found"),
1719
+ defaultMessage: "No Richtext content have been found."
1720
+ }),
1721
+ qualityVerdict: qualityVerdict.improvements
1722
+ };
1723
+ } else if (wordCount < 300) {
1724
+ status = {
1725
+ message: formatMessage({
1726
+ id: getTrad("SEOChecks.wordCountCheck.300"),
1727
+ defaultMessage: "Your 1st level Richtext contents have less than 300 words."
1728
+ }),
1729
+ qualityVerdict: qualityVerdict.improvements
1730
+ };
1731
+ }
1732
+ if (!isSameCheckStatus(status, checks.wordCount) && dispatch) {
1733
+ dispatch({
1734
+ type: "UPDATE_PONCTUAL",
1735
+ value: { ...status, entity: "wordCount" }
1736
+ });
1737
+ }
1738
+ }, [wordCount]);
1739
+ return /* @__PURE__ */ jsxRuntime.jsx(
1740
+ SEOAccordion,
1741
+ {
1742
+ title: formatMessage({
1743
+ id: getTrad("SEOModal.summary-title.word-counter"),
1744
+ defaultMessage: "Word Counter"
1745
+ }),
1746
+ status: checks.wordCount,
1747
+ label: formatMessage({
1748
+ id: getTrad("SEOChecks.wordCountCheck.label"),
1749
+ defaultMessage: "Your content should be as high quality as possible, with relevant and unique information. You entry requires a minimum of paragraphs, and therefore of words."
1750
+ }),
1751
+ component: typeof wordCount === "number" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral100", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: [
1752
+ formatMessage({
1753
+ id: getTrad("SEOChecks.wordCountCheck.words"),
1754
+ defaultMessage: "Words:"
1755
+ }),
1756
+ " ",
1757
+ wordCount
1758
+ ] }) })
1759
+ }
1760
+ );
1761
+ };
1762
+ const BasicMetaChecksSection = ({
1763
+ seo,
1764
+ checks,
1765
+ wordCount,
1766
+ keywordsDensity
1767
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1768
+ checks?.metaTitle && /* @__PURE__ */ jsxRuntime.jsx(MetaTitleCheck, { metaTitle: seo.metaTitle ?? null, checks }),
1769
+ checks?.metaDescription && /* @__PURE__ */ jsxRuntime.jsx(
1770
+ MetaDescriptionCheck,
1771
+ {
1772
+ metaDescription: seo.metaDescription ?? null,
1773
+ checks
1774
+ }
1775
+ ),
1776
+ checks?.wordCount && /* @__PURE__ */ jsxRuntime.jsx(WordCountCheck, { wordCount, checks }),
1777
+ checks?.keywordsDensity && /* @__PURE__ */ jsxRuntime.jsx(KeywordDensityCheck, { keywordsDensity, checks })
1778
+ ] });
1779
+ const TechnicalMetaChecksSection = ({
1780
+ seo,
1781
+ checks
1782
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1783
+ checks?.openGraph && /* @__PURE__ */ jsxRuntime.jsx(OpenGraphCheck, { openGraph: seo.openGraph ?? null, checks }),
1784
+ checks?.canonicalUrl && /* @__PURE__ */ jsxRuntime.jsx(
1785
+ CanonicalUrlCheck,
1786
+ {
1787
+ canonicalUrl: seo.canonicalURL ?? null,
1788
+ checks
1789
+ }
1790
+ ),
1791
+ checks?.structuredData && /* @__PURE__ */ jsxRuntime.jsx(
1792
+ StructuredDataCheck,
1793
+ {
1794
+ structuredData: seo.structuredData ?? null,
1795
+ checks
1796
+ }
1797
+ ),
1798
+ checks?.metaRobots && /* @__PURE__ */ jsxRuntime.jsx(MetaRobotCheck, { metaRobots: seo.metaRobots ?? null, checks })
1799
+ ] });
1800
+ const MediaAndFreshnessChecksSection = ({
1801
+ checks,
1802
+ emptyAltCount,
1803
+ updatedAt
1804
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1805
+ checks?.alternativeText && /* @__PURE__ */ jsxRuntime.jsx(
1806
+ AlternativeTextCheck,
1807
+ {
1808
+ intersections: emptyAltCount?.intersections ?? null,
1809
+ richTextAlts: [
1810
+ ...emptyAltCount?.richTextAlts ?? [],
1811
+ ...emptyAltCount?.blockTextAlts ?? []
1812
+ ],
1813
+ altTexts: emptyAltCount?.altTexts ?? [],
1814
+ checks
1815
+ }
1816
+ ),
1817
+ checks?.lastUpdatedAt && /* @__PURE__ */ jsxRuntime.jsx(LastUpdatedAtCheck, { updatedAt, checks })
1818
+ ] });
1819
+ const SeoChecks = ({
1820
+ updatedAt,
1821
+ modifiedData,
1822
+ components,
1823
+ contentType,
1824
+ checks
1825
+ }) => {
1826
+ const { formatMessage } = reactIntl.useIntl();
1827
+ const { wordCount, keywordsDensity, emptyAltCount } = getRichTextData(
1828
+ modifiedData,
1829
+ components,
1830
+ contentType
1831
+ );
1832
+ const seo = modifiedData?.seo ?? null;
1833
+ const hasSeo = Boolean(seo && Object.keys(seo).length > 0);
1834
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { labelledBy: "title", children: [
1835
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(
1836
+ designSystem.Typography,
1837
+ {
1838
+ fontWeight: "bold",
1839
+ textColor: "neutral800",
1840
+ tag: "h2",
1841
+ id: "title",
1842
+ children: formatMessage({
1843
+ id: getTrad("Button.seo-analyze"),
1844
+ defaultMessage: "SEO Analyze"
1845
+ })
1846
+ }
1847
+ ) }),
1848
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: hasSeo && seo ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
1849
+ /* @__PURE__ */ jsxRuntime.jsx(
1850
+ BasicMetaChecksSection,
1851
+ {
1852
+ seo,
1853
+ checks,
1854
+ wordCount,
1855
+ keywordsDensity
1856
+ }
1857
+ ),
1858
+ /* @__PURE__ */ jsxRuntime.jsx(TechnicalMetaChecksSection, { seo, checks }),
1859
+ /* @__PURE__ */ jsxRuntime.jsx(
1860
+ MediaAndFreshnessChecksSection,
1861
+ {
1862
+ checks,
1863
+ emptyAltCount,
1864
+ updatedAt
1865
+ }
1866
+ )
1867
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingLeft: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
1868
+ designSystem.EmptyStateLayout,
1869
+ {
1870
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Illo, {}),
1871
+ content: formatMessage({
1872
+ id: getTrad("Modal.seo-component-empty"),
1873
+ defaultMessage: "Your SEO component is empty..."
1874
+ })
1875
+ }
1876
+ ) }) })
1877
+ ] });
1878
+ };
1879
+ const KeywordCheck = ({ item, keywords, label }) => {
1880
+ const { formatMessage } = reactIntl.useIntl();
1881
+ const normalizedKeywords = keywords.split(",").map((x) => x.trim()).filter((x) => x.length > 0);
1882
+ const matches = normalizedKeywords.filter(
1883
+ (x) => item.toLowerCase().includes(x.toLowerCase())
1884
+ );
1885
+ const getIcon = () => {
1886
+ if (matches.length === 0) {
1887
+ return /* @__PURE__ */ jsxRuntime.jsx(icons.WarningCircle, { "aria-hidden": true, fill: "warning600" });
1888
+ }
1889
+ return /* @__PURE__ */ jsxRuntime.jsx(icons.CheckCircle, { "aria-hidden": true, fill: "success600" });
1890
+ };
1891
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { spacing: 1, horizontal: true, children: [
1892
+ getIcon(),
1893
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
1894
+ {
1895
+ id: getTrad("SEOSocialPreview.keyword-check"),
1896
+ defaultMessage: "You use {count} keyword(s) in your {label} ({matches})"
1897
+ },
1898
+ {
1899
+ count: matches.length,
1900
+ label,
1901
+ matches: matches.join(", ").trim()
1902
+ }
1903
+ ) })
1904
+ ] });
1905
+ };
1906
+ const MetaChecks = ({
1907
+ item,
1908
+ max,
1909
+ label,
1910
+ minLimit = false
1911
+ }) => {
1912
+ const { formatMessage } = reactIntl.useIntl();
1913
+ const getIcon = () => {
1914
+ if (item.length >= max || label === "title" && item.length === 0 || minLimit && label === "description" && item.length <= 50) {
1915
+ return /* @__PURE__ */ jsxRuntime.jsx(icons.WarningCircle, { "aria-hidden": true, fill: "warning600" });
1916
+ }
1917
+ return /* @__PURE__ */ jsxRuntime.jsx(icons.CheckCircle, { "aria-hidden": true, fill: "success600" });
1918
+ };
1919
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { spacing: 1, horizontal: true, children: [
1920
+ getIcon(),
1921
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
1922
+ {
1923
+ id: getTrad("SEOSocialPreview.meta-length-check"),
1924
+ defaultMessage: "Your {label} is {length} characters long (max {max})"
1925
+ },
1926
+ {
1927
+ label,
1928
+ length: item.length || 0,
1929
+ max
1930
+ }
1931
+ ) })
1932
+ ] });
1933
+ };
1934
+ const Serp = ({ metaTitle, metaDescription }) => {
1935
+ const metaTitleToDisplay = metaTitle.length > 60 ? `${metaTitle.substring(0, 56)} ...` : metaTitle;
1936
+ const metaDescriptionToDisplay = metaDescription.length > 160 ? `${metaDescription.substring(0, 156)} ...` : metaDescription;
1937
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 4, background: "neutral100", width: "100%", maxWidth: "580px", children: [
1938
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", textColor: "primary600", children: metaTitleToDisplay }),
1939
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: metaDescriptionToDisplay }) })
1940
+ ] });
1941
+ };
1942
+ const BrowserPreview = ({ modifiedData }) => {
1943
+ const { formatMessage } = reactIntl.useIntl();
1944
+ const seo = modifiedData?.seo ?? null;
1945
+ const metaTitle = seo?.metaTitle ?? null;
1946
+ const metaDescription = seo?.metaDescription ?? null;
1947
+ const keywords = seo?.keywords ?? null;
1948
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { labelledBy: "title", children: [
1949
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(
1950
+ designSystem.Typography,
1951
+ {
1952
+ fontWeight: "bold",
1953
+ textColor: "neutral800",
1954
+ tag: "h2",
1955
+ id: "title",
1956
+ children: formatMessage({
1957
+ id: getTrad("Button.browser-preview"),
1958
+ defaultMessage: "Browser Preview"
1959
+ })
1960
+ }
1961
+ ) }),
1962
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: seo ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, alignItems: "flex-start", children: [
1963
+ metaTitle && metaDescription && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingBottom: 6, children: /* @__PURE__ */ jsxRuntime.jsx(Serp, { metaTitle, metaDescription }) }),
1964
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, { marginBottom: 4, width: "100%" }),
1965
+ metaTitle && /* @__PURE__ */ jsxRuntime.jsx(MetaChecks, { item: metaTitle, max: 60, label: "title" }),
1966
+ metaDescription && /* @__PURE__ */ jsxRuntime.jsx(
1967
+ MetaChecks,
1968
+ {
1969
+ item: metaDescription,
1970
+ max: 160,
1971
+ label: "description",
1972
+ minLimit: true
1973
+ }
1974
+ ),
1975
+ keywords && /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
1976
+ metaTitle && /* @__PURE__ */ jsxRuntime.jsx(
1977
+ KeywordCheck,
1978
+ {
1979
+ item: metaTitle,
1980
+ keywords,
1981
+ label: "title"
1982
+ }
1983
+ ),
1984
+ metaDescription && /* @__PURE__ */ jsxRuntime.jsx(
1985
+ KeywordCheck,
1986
+ {
1987
+ item: metaDescription,
1988
+ keywords,
1989
+ label: "description"
1990
+ }
1991
+ )
1992
+ ] })
1993
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingLeft: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
1994
+ designSystem.EmptyStateLayout,
1995
+ {
1996
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Illo, {}),
1997
+ content: formatMessage({
1998
+ id: getTrad("Modal.seo-component-empty"),
1999
+ defaultMessage: "Your SEO component is empty..."
2000
+ })
2001
+ }
2002
+ ) }) })
2003
+ ] });
2004
+ };
2005
+ const toSafeImageUrl$2 = (rawUrl) => {
2006
+ if (typeof rawUrl !== "string") return "";
2007
+ const trimmed = rawUrl.trim();
2008
+ if (trimmed.startsWith("https://") || trimmed.startsWith("http://") || trimmed.startsWith("/")) {
2009
+ return trimmed;
2010
+ }
2011
+ return "";
2012
+ };
2013
+ const FacebookOGPreview = ({
2014
+ image,
2015
+ title,
2016
+ description
2017
+ }) => {
2018
+ const safeSrc = toSafeImageUrl$2(image?.url);
2019
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2020
+ "div",
2021
+ {
2022
+ style: {
2023
+ width: "100%",
2024
+ maxWidth: "500px",
2025
+ border: "1px solid #dadde1",
2026
+ fontFamily: "Helvetica, Arial, sans-serif",
2027
+ cursor: "pointer",
2028
+ margin: "auto",
2029
+ marginBottom: "40px"
2030
+ },
2031
+ children: [
2032
+ /* @__PURE__ */ jsxRuntime.jsx(
2033
+ "div",
2034
+ {
2035
+ style: {
2036
+ borderBottom: "1px solid #dadde1",
2037
+ backgroundSize: "cover",
2038
+ backgroundPosition: "center"
2039
+ },
2040
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2041
+ "div",
2042
+ {
2043
+ style: { width: "100%", position: "relative", paddingTop: "52.5%" },
2044
+ children: safeSrc ? /* @__PURE__ */ jsxRuntime.jsx(
2045
+ "img",
2046
+ {
2047
+ src: safeSrc,
2048
+ alt: title,
2049
+ style: {
2050
+ position: "absolute",
2051
+ top: 0,
2052
+ left: 0,
2053
+ width: "100%",
2054
+ height: "100%",
2055
+ objectFit: "cover"
2056
+ }
2057
+ }
2058
+ ) : null
2059
+ }
2060
+ )
2061
+ }
2062
+ ),
2063
+ /* @__PURE__ */ jsxRuntime.jsxs(
2064
+ "div",
2065
+ {
2066
+ style: {
2067
+ backgroundColor: "#f2f3f5",
2068
+ padding: "12px 10px",
2069
+ lineHeight: "1.4"
2070
+ },
2071
+ children: [
2072
+ /* @__PURE__ */ jsxRuntime.jsx(
2073
+ "div",
2074
+ {
2075
+ style: {
2076
+ fontSize: "12px",
2077
+ textTransform: "uppercase",
2078
+ color: "#606770",
2079
+ marginBottom: "4px",
2080
+ whiteSpace: "nowrap",
2081
+ overflow: "hidden",
2082
+ textOverflow: "ellipsis"
2083
+ },
2084
+ children: "your-site.io"
2085
+ }
2086
+ ),
2087
+ /* @__PURE__ */ jsxRuntime.jsx(
2088
+ "div",
2089
+ {
2090
+ style: {
2091
+ fontSize: "16px",
2092
+ fontWeight: "bold",
2093
+ color: "#1d2129",
2094
+ marginBottom: "4px",
2095
+ overflow: "hidden",
2096
+ textOverflow: "ellipsis",
2097
+ whiteSpace: "nowrap"
2098
+ },
2099
+ children: title.substring(0, 65)
2100
+ }
2101
+ ),
2102
+ /* @__PURE__ */ jsxRuntime.jsx(
2103
+ "div",
2104
+ {
2105
+ style: {
2106
+ fontSize: "14px",
2107
+ color: "#606770",
2108
+ maxHeight: "40px",
2109
+ overflow: "hidden",
2110
+ textOverflow: "ellipsis",
2111
+ WebkitLineClamp: 2,
2112
+ WebkitBoxOrient: "vertical",
2113
+ display: "-webkit-box"
2114
+ },
2115
+ children: description.substring(0, 150)
2116
+ }
2117
+ )
2118
+ ]
2119
+ }
2120
+ )
2121
+ ]
2122
+ }
2123
+ );
2124
+ };
2125
+ const toSafeImageUrl$1 = (rawUrl) => {
2126
+ if (typeof rawUrl !== "string") return "";
2127
+ const trimmed = rawUrl.trim();
2128
+ if (trimmed.startsWith("https://") || trimmed.startsWith("http://") || trimmed.startsWith("/")) {
2129
+ return trimmed;
2130
+ }
2131
+ return "";
2132
+ };
2133
+ const LinkedInOGPreview = ({ image, title }) => {
2134
+ const safeSrc = toSafeImageUrl$1(image?.url);
2135
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2136
+ "div",
2137
+ {
2138
+ style: {
2139
+ width: "100%",
2140
+ maxWidth: "500px",
2141
+ borderRadius: "2px",
2142
+ boxShadow: "0 0 0 1px rgba(0,0,0,.15),0 2px 3px rgba(0,0,0,.2)",
2143
+ fontFamily: "Arial, sans-serif",
2144
+ overflow: "hidden",
2145
+ cursor: "pointer",
2146
+ margin: "auto",
2147
+ marginBottom: "40px"
2148
+ },
2149
+ children: [
2150
+ /* @__PURE__ */ jsxRuntime.jsx(
2151
+ "div",
2152
+ {
2153
+ style: {
2154
+ backgroundSize: "cover",
2155
+ backgroundPosition: "center",
2156
+ backgroundRepeat: "no-repeat"
2157
+ },
2158
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2159
+ "div",
2160
+ {
2161
+ style: { width: "100%", position: "relative", paddingTop: "52.5%" },
2162
+ children: safeSrc ? /* @__PURE__ */ jsxRuntime.jsx(
2163
+ "img",
2164
+ {
2165
+ src: safeSrc,
2166
+ alt: title,
2167
+ style: {
2168
+ position: "absolute",
2169
+ top: 0,
2170
+ left: 0,
2171
+ width: "100%",
2172
+ height: "100%",
2173
+ objectFit: "cover"
2174
+ }
2175
+ }
2176
+ ) : null
2177
+ }
2178
+ )
2179
+ }
2180
+ ),
2181
+ /* @__PURE__ */ jsxRuntime.jsxs(
2182
+ "div",
2183
+ {
2184
+ style: {
2185
+ backgroundColor: "#ffffff",
2186
+ padding: "12px 16px",
2187
+ lineHeight: "1.5",
2188
+ borderTop: "1px solid #ccd0d5"
2189
+ },
2190
+ children: [
2191
+ /* @__PURE__ */ jsxRuntime.jsx(
2192
+ "div",
2193
+ {
2194
+ style: {
2195
+ fontSize: "18px",
2196
+ fontWeight: "bold",
2197
+ color: "#333333",
2198
+ marginBottom: "2px",
2199
+ overflow: "hidden",
2200
+ textOverflow: "ellipsis",
2201
+ whiteSpace: "nowrap"
2202
+ },
2203
+ children: title.substring(0, 70)
2204
+ }
2205
+ ),
2206
+ /* @__PURE__ */ jsxRuntime.jsx(
2207
+ "div",
2208
+ {
2209
+ style: {
2210
+ fontSize: "12px",
2211
+ color: "#666666",
2212
+ marginBottom: "4px",
2213
+ whiteSpace: "nowrap",
2214
+ overflow: "hidden",
2215
+ textOverflow: "ellipsis"
2216
+ },
2217
+ children: "your-site.io"
2218
+ }
2219
+ )
2220
+ ]
2221
+ }
2222
+ )
2223
+ ]
2224
+ }
2225
+ );
2226
+ };
2227
+ const toSafeImageUrl = (rawUrl) => {
2228
+ if (typeof rawUrl !== "string") return "";
2229
+ const trimmed = rawUrl.trim();
2230
+ if (trimmed.startsWith("https://") || trimmed.startsWith("http://") || trimmed.startsWith("/")) {
2231
+ return trimmed;
2232
+ }
2233
+ return "";
2234
+ };
2235
+ const TwitterOGPreview = ({
2236
+ image,
2237
+ title,
2238
+ description
2239
+ }) => {
2240
+ const safeSrc = toSafeImageUrl(image?.url);
2241
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2242
+ "div",
2243
+ {
2244
+ style: {
2245
+ width: "100%",
2246
+ maxWidth: "500px",
2247
+ border: "1px solid #e1e8ed",
2248
+ fontFamily: "Arial, sans-serif",
2249
+ borderRadius: "8px",
2250
+ overflow: "hidden",
2251
+ cursor: "pointer",
2252
+ margin: "auto",
2253
+ marginBottom: "40px"
2254
+ },
2255
+ children: [
2256
+ /* @__PURE__ */ jsxRuntime.jsx(
2257
+ "div",
2258
+ {
2259
+ style: {
2260
+ backgroundSize: "cover",
2261
+ backgroundPosition: "center",
2262
+ backgroundRepeat: "no-repeat"
2263
+ },
2264
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2265
+ "div",
2266
+ {
2267
+ style: { width: "100%", position: "relative", paddingTop: "52.5%" },
2268
+ children: safeSrc ? /* @__PURE__ */ jsxRuntime.jsx(
2269
+ "img",
2270
+ {
2271
+ src: safeSrc,
2272
+ alt: title,
2273
+ style: {
2274
+ position: "absolute",
2275
+ top: 0,
2276
+ left: 0,
2277
+ width: "100%",
2278
+ height: "100%",
2279
+ objectFit: "cover"
2280
+ }
2281
+ }
2282
+ ) : null
2283
+ }
2284
+ )
2285
+ }
2286
+ ),
2287
+ /* @__PURE__ */ jsxRuntime.jsxs(
2288
+ "div",
2289
+ {
2290
+ style: {
2291
+ backgroundColor: "#ffffff",
2292
+ padding: "12px 10px",
2293
+ lineHeight: "1.4",
2294
+ borderTop: "1px solid #e1e8ed"
2295
+ },
2296
+ children: [
2297
+ /* @__PURE__ */ jsxRuntime.jsx(
2298
+ "div",
2299
+ {
2300
+ style: {
2301
+ fontSize: "12px",
2302
+ color: "#657786",
2303
+ marginBottom: "4px",
2304
+ whiteSpace: "nowrap",
2305
+ overflow: "hidden",
2306
+ textOverflow: "ellipsis"
2307
+ },
2308
+ children: "your-site.io"
2309
+ }
2310
+ ),
2311
+ /* @__PURE__ */ jsxRuntime.jsx(
2312
+ "div",
2313
+ {
2314
+ style: {
2315
+ fontSize: "16px",
2316
+ fontWeight: "bold",
2317
+ color: "#14171A",
2318
+ marginBottom: "4px",
2319
+ overflow: "hidden",
2320
+ textOverflow: "ellipsis",
2321
+ whiteSpace: "nowrap"
2322
+ },
2323
+ children: title.substring(0, 70)
2324
+ }
2325
+ ),
2326
+ /* @__PURE__ */ jsxRuntime.jsx(
2327
+ "div",
2328
+ {
2329
+ style: {
2330
+ fontSize: "14px",
2331
+ color: "#657786",
2332
+ maxHeight: "40px",
2333
+ overflow: "hidden",
2334
+ textOverflow: "ellipsis",
2335
+ WebkitLineClamp: 2,
2336
+ WebkitBoxOrient: "vertical",
2337
+ display: "-webkit-box"
2338
+ },
2339
+ children: description.substring(0, 200)
2340
+ }
2341
+ )
2342
+ ]
2343
+ }
2344
+ )
2345
+ ]
2346
+ }
2347
+ );
2348
+ };
2349
+ const OpenGraphPreview = ({ modifiedData }) => {
2350
+ const { formatMessage } = reactIntl.useIntl();
2351
+ const openGraph = modifiedData?.seo?.openGraph;
2352
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { labelledBy: "title", children: [
2353
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(
2354
+ designSystem.Typography,
2355
+ {
2356
+ fontWeight: "bold",
2357
+ textColor: "neutral800",
2358
+ tag: "h2",
2359
+ id: "title",
2360
+ children: formatMessage({
2361
+ id: getTrad("Button.open-graph-preview"),
2362
+ defaultMessage: "Open Graph Preview"
2363
+ })
2364
+ }
2365
+ ) }),
2366
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: openGraph?.ogTitle && openGraph?.ogDescription && openGraph?.ogImage?.url ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2367
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: formatMessage({
2368
+ id: getTrad("Social-network.platform.facebook"),
2369
+ defaultMessage: "Facebook"
2370
+ }) }) }) }),
2371
+ /* @__PURE__ */ jsxRuntime.jsx(
2372
+ FacebookOGPreview,
2373
+ {
2374
+ title: openGraph.ogTitle,
2375
+ description: openGraph.ogDescription,
2376
+ image: openGraph.ogImage
2377
+ }
2378
+ ),
2379
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: formatMessage({
2380
+ id: getTrad("Social-network.platform.twitter"),
2381
+ defaultMessage: "X (Twitter)"
2382
+ }) }) }) }),
2383
+ /* @__PURE__ */ jsxRuntime.jsx(
2384
+ TwitterOGPreview,
2385
+ {
2386
+ title: openGraph.ogTitle,
2387
+ description: openGraph.ogDescription,
2388
+ image: openGraph.ogImage
2389
+ }
2390
+ ),
2391
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: formatMessage({
2392
+ id: getTrad("Social-network.platform.linkedin"),
2393
+ defaultMessage: "LinkedIn"
2394
+ }) }) }) }),
2395
+ /* @__PURE__ */ jsxRuntime.jsx(
2396
+ LinkedInOGPreview,
2397
+ {
2398
+ title: openGraph.ogTitle,
2399
+ description: openGraph.ogDescription,
2400
+ image: openGraph.ogImage
2401
+ }
2402
+ )
2403
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingLeft: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
2404
+ designSystem.EmptyStateLayout,
2405
+ {
2406
+ icon: /* @__PURE__ */ jsxRuntime.jsx(Illo, {}),
2407
+ content: formatMessage({
2408
+ id: getTrad("Modal.seo-component-empty-open-graph"),
2409
+ defaultMessage: "The Open Graph metadata is empty..."
2410
+ })
2411
+ }
2412
+ ) }) })
2413
+ ] });
2414
+ };
2415
+ const PreviewChecks = ({ checks }) => {
2416
+ const { formatMessage } = reactIntl.useIntl();
2417
+ const values = Object.values(checks || {}).filter(
2418
+ (c) => Boolean(c && typeof c === "object" && "qualityVerdict" in c)
2419
+ );
2420
+ const good = values.filter(
2421
+ (check) => check.qualityVerdict === qualityVerdict.good
2422
+ ).length;
2423
+ const improvements = values.filter(
2424
+ (check) => check.qualityVerdict === qualityVerdict.improvements
2425
+ ).length;
2426
+ const bad = values.filter(
2427
+ (check) => check.qualityVerdict === qualityVerdict.bad
2428
+ ).length;
2429
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingTop: 2, children: [
2430
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
2431
+ /* @__PURE__ */ jsxRuntime.jsx(icons.CheckCircle, { "aria-hidden": true, fill: "success600" }),
2432
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { children: [
2433
+ formatMessage({
2434
+ id: getTrad("Good"),
2435
+ defaultMessage: "Good"
2436
+ }),
2437
+ `: ${good}`
2438
+ ] })
2439
+ ] }, qualityVerdict.good),
2440
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
2441
+ /* @__PURE__ */ jsxRuntime.jsx(icons.WarningCircle, { "aria-hidden": true, fill: "warning600" }),
2442
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { children: [
2443
+ formatMessage({
2444
+ id: getTrad("Improvements"),
2445
+ defaultMessage: "Improvements"
2446
+ }),
2447
+ `: ${improvements}`
2448
+ ] })
2449
+ ] }, qualityVerdict.improvements),
2450
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
2451
+ /* @__PURE__ */ jsxRuntime.jsx(icons.CrossCircle, { "aria-hidden": true, fill: "danger600" }),
2452
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { children: [
2453
+ formatMessage({
2454
+ id: getTrad("Bad"),
2455
+ defaultMessage: "Bad"
2456
+ }),
2457
+ `: ${bad}`
2458
+ ] })
2459
+ ] }, qualityVerdict.bad)
2460
+ ] });
2461
+ };
2462
+ function reducer(state, action) {
2463
+ switch (action.type) {
2464
+ case "UPDATE_PONCTUAL":
2465
+ return {
2466
+ ...state,
2467
+ [action.value.entity]: {
2468
+ qualityVerdict: action.value.qualityVerdict,
2469
+ message: action.value.message
2470
+ }
2471
+ };
2472
+ case "UPDATE_FOR_PREVIEW":
2473
+ return action.value;
2474
+ default:
2475
+ return state;
2476
+ }
2477
+ }
2478
+ const initialState = {
2479
+ preview: true
2480
+ };
2481
+ const SeoCheckerContext = React__namespace.createContext(null);
2482
+ const buildMetaChecksPreview = (seoChecks, currentModifiedData, updatedAt) => ({
2483
+ ...seoChecks.metaTitle && {
2484
+ metaTitle: getMetaTitleCheckPreview(currentModifiedData)
2485
+ },
2486
+ ...seoChecks.metaDescription && {
2487
+ metaDescription: getMetaDescriptionPreview(currentModifiedData)
2488
+ },
2489
+ ...seoChecks.metaRobots && {
2490
+ metaRobots: metaRobotPreview(currentModifiedData)
2491
+ },
2492
+ ...seoChecks.openGraph && {
2493
+ openGraph: openGraphPreview(currentModifiedData)
2494
+ },
2495
+ ...seoChecks.canonicalUrl && {
2496
+ canonicalUrl: canonicalUrlPreview(currentModifiedData)
2497
+ },
2498
+ ...seoChecks.structuredData && {
2499
+ structuredData: structuredDataPreview(currentModifiedData)
2500
+ },
2501
+ ...seoChecks.lastUpdatedAt && {
2502
+ lastUpdatedAt: lastUpdatedAtPreview(currentModifiedData, updatedAt)
2503
+ }
2504
+ });
2505
+ const computeAllChecks = (seoChecks, currentModifiedData, currentComponents, currentContentType, updatedAt) => {
2506
+ const { wordCount, keywordsDensity, emptyAltCount } = getRichTextData(
2507
+ currentModifiedData,
2508
+ currentComponents,
2509
+ currentContentType
2510
+ );
2511
+ return {
2512
+ ...buildMetaChecksPreview(seoChecks, currentModifiedData, updatedAt),
2513
+ ...seoChecks.wordCount && {
2514
+ wordCount: getWordCountPreview(wordCount)
2515
+ },
2516
+ ...seoChecks.alternativeText && {
2517
+ alternativeText: getAlternativeTextPreview(emptyAltCount)
2518
+ },
2519
+ ...seoChecks.keywordDensity && {
2520
+ keywordsDensity: getKeywordDensityPreview(keywordsDensity)
2521
+ }
2522
+ };
2523
+ };
2524
+ const Summary = () => {
2525
+ const { formatMessage } = reactIntl.useIntl();
2526
+ const { getSettings } = useSettingsApi();
2527
+ const [isLoading, setIsLoading] = React__namespace.useState(true);
2528
+ const [seoChecksConfig, setSeoChecksConfig] = React__namespace.useState(DEFAULT_SEO_CHECKS);
2529
+ const [checks, dispatch] = React__namespace.useReducer(reducer, initialState);
2530
+ const { model, collectionType, id, form, contentType, components } = admin.unstable_useContentManagerContext();
2531
+ const { values: modifiedData } = form;
2532
+ const { metaTitle, metaDescription, openGraph } = modifiedData.seo ?? {};
2533
+ const { document } = admin.unstable_useDocument({
2534
+ model,
2535
+ collectionType,
2536
+ documentId: id
2537
+ });
2538
+ const updatedAt = document?.updatedAt ?? modifiedData?.updatedAt ?? null;
2539
+ const uid = contentType?.uid ?? "";
2540
+ React__namespace.useEffect(() => {
2541
+ let isMounted = true;
2542
+ void getSettings().then(({ data: defaultSettings }) => {
2543
+ if (!isMounted) return;
2544
+ const resolved = defaultSettings?.[uid]?.seoChecks ?? DEFAULT_SEO_CHECKS;
2545
+ setSeoChecksConfig(resolved);
2546
+ }).catch(() => {
2547
+ if (!isMounted) return;
2548
+ setSeoChecksConfig(DEFAULT_SEO_CHECKS);
2549
+ }).finally(() => {
2550
+ if (isMounted) {
2551
+ setIsLoading(false);
2552
+ }
2553
+ });
2554
+ return () => {
2555
+ isMounted = false;
2556
+ };
2557
+ }, [uid]);
2558
+ React__namespace.useEffect(() => {
2559
+ const status = computeAllChecks(
2560
+ seoChecksConfig,
2561
+ modifiedData,
2562
+ components,
2563
+ contentType,
2564
+ updatedAt
2565
+ );
2566
+ dispatch({
2567
+ type: "UPDATE_FOR_PREVIEW",
2568
+ value: status
2569
+ });
2570
+ }, [seoChecksConfig, modifiedData, components, contentType, updatedAt]);
2571
+ return /* @__PURE__ */ jsxRuntime.jsx(SeoCheckerContext.Provider, { value: dispatch, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingTop: 4, width: "100%", children: [
2572
+ /* @__PURE__ */ jsxRuntime.jsx(
2573
+ designSystem.Typography,
2574
+ {
2575
+ tag: "h2",
2576
+ variant: "sigma",
2577
+ textTransform: "uppercase",
2578
+ textColor: "neutral600",
2579
+ children: formatMessage({
2580
+ id: getTrad("Plugin.name"),
2581
+ defaultMessage: "SEO Plugin"
2582
+ })
2583
+ }
2584
+ ),
2585
+ metaTitle && metaDescription && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Root, { children: [
2586
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { fullWidth: true, variant: "secondary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Eye, {}), children: formatMessage({
2587
+ id: getTrad("Button.browser-preview"),
2588
+ defaultMessage: "Browser Preview"
2589
+ }) }) }) }),
2590
+ /* @__PURE__ */ jsxRuntime.jsx(BrowserPreview, { modifiedData })
2591
+ ] }),
2592
+ openGraph && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Root, { children: [
2593
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { fullWidth: true, variant: "secondary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Eye, {}), children: formatMessage({
2594
+ id: getTrad("Button.open-graph-preview"),
2595
+ defaultMessage: "Open Graph Preview"
2596
+ }) }) }) }),
2597
+ /* @__PURE__ */ jsxRuntime.jsx(OpenGraphPreview, { modifiedData })
2598
+ ] }),
2599
+ !isLoading && /* @__PURE__ */ jsxRuntime.jsx(PreviewChecks, { checks }),
2600
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Root, { children: [
2601
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.TextButton, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowRight, {}), children: formatMessage({
2602
+ id: getTrad("Button.see-details"),
2603
+ defaultMessage: "SEE DETAILS"
2604
+ }) }) }) }),
2605
+ /* @__PURE__ */ jsxRuntime.jsx(
2606
+ SeoChecks,
2607
+ {
2608
+ updatedAt: document?.updatedAt ?? null,
2609
+ modifiedData,
2610
+ components,
2611
+ contentType,
2612
+ checks
2613
+ }
2614
+ )
2615
+ ] })
2616
+ ] }) });
2617
+ };
2618
+ const SeoChecker = () => {
2619
+ const {
2620
+ form: { values }
2621
+ } = admin.unstable_useContentManagerContext();
2622
+ if (values?.seo) {
2623
+ return /* @__PURE__ */ jsxRuntime.jsx(Summary, {});
2624
+ }
2625
+ return /* @__PURE__ */ jsxRuntime.jsx(React__namespace.Fragment, {});
2626
+ };
2627
+ const Initializer = ({ setPlugin }) => {
2628
+ const ref = React__namespace.useRef(void 0);
2629
+ ref.current = setPlugin;
2630
+ React__namespace.useEffect(() => {
2631
+ if (ref.current) {
2632
+ ref.current(pluginId);
2633
+ }
2634
+ }, []);
2635
+ return null;
2636
+ };
2637
+ const pluginPermissions = {
2638
+ main: [{ action: "plugin::seo.read", subject: null }]
2639
+ };
2640
+ const Good$5 = "جيد";
2641
+ const Improvements$5 = "تحسينات ممكنة";
2642
+ const Bad$5 = "ضعيف";
2643
+ const Information$5 = "معلومات";
2644
+ const ar = {
2645
+ "Plugin.name": "إضافة تحسين محركات البحث (SEO)",
2646
+ "SEOPage.header.button.edit-component": "تعديل مكوّن SEO",
2647
+ "SEOPage.header.button.back": "رجوع",
2648
+ "SEOPage.header.title": "تحسين محركات البحث (SEO)",
2649
+ "SEOPage.header.subtitle": "حسّن محتواك ليكون متوافقاً مع محركات البحث",
2650
+ "SEOPage.empty-component-layout.github-error": "مستودع GitHub هذا لا يحتوي على مكوّن shared.seo أو shared.open-graph",
2651
+ "SEOPage.empty-component-layout.regexp-error": "يرجى تقديم رابط مستودع GitHub صالح",
2652
+ "SEOPage.empty-component-layout.no-component": "ليس لديك أي مكوّن SEO حتى الآن",
2653
+ "SEOPage.empty-component-layout.input.tooltip": "يحتوي مستودع strapi/components على GitHub على مكوّن SEO الافتراضي",
2654
+ "SEOPage.info.information": "تأكد من استخدام المكوّن المستورد shared.seo وتسميته 'seo' وإضافته في المستوى الجذري لنوع المحتوى الخاص بك.",
2655
+ "SEOPage.info.no-collection-types": "ليس لديك أي أنواع مجموعات (Collection Types) حتى الآن...",
2656
+ "SEOPage.info.no-single-types": "ليس لديك أي أنواع فردية (Single Types) حتى الآن...",
2657
+ "SEOPage.info.no-plugin-content-type": "ليس لديك أي نوع محتوى تابع لإضافة حتى الآن...",
2658
+ "SEOPage.info.create-collection-type": "أنشئ أول نوع مجموعة",
2659
+ "SEOPage.info.create-single-type": "أنشئ أول نوع فردي",
2660
+ "SEOPage.info.added": "تمت الإضافة",
2661
+ "SEOPage.info.add": "إضافة مكوّن",
2662
+ "SEOPage.info.settings": "الإعدادات",
2663
+ "SEOPage.info.settings.meta-title-check": "العنوان الوصفي (Meta Title)",
2664
+ "SEOPage.info.settings.meta-description-check": "الوصف التعريفي (Meta Description)",
2665
+ "SEOPage.info.settings.meta-robots-check": "وسوم الروبوتات (Meta Robots)",
2666
+ "SEOPage.info.settings.open-graph-check": "Open Graph",
2667
+ "SEOPage.info.settings.word-count-check": "عدد الكلمات",
2668
+ "SEOPage.info.settings.canonical-url-check": "الرابط الأساسي (Canonical URL)",
2669
+ "SEOPage.info.settings.keyword-density-check": "كثافة الكلمات المفتاحية",
2670
+ "SEOPage.info.settings.structured-data-check": "البيانات المنظمة (Structured Data)",
2671
+ "SEOPage.info.settings.alternative-text-check": "النص البديل (Alt Text)",
2672
+ "SEOPage.info.settings.last-updated-at-check": "تاريخ آخر تحديث",
2673
+ "SEOPage.info.settings.cancel.button": "إلغاء",
2674
+ "SEOPage.info.settings.save.button": "حفظ",
2675
+ "SEOPage.info.settings.information": "تعطيل فحوصات SEO لنوع المحتوى المحدد هذا.",
2676
+ "SEOPage.tab.collection-type-title": "أنواع المجموعات",
2677
+ "SEOPage.tab.single-type-title": "الأنواع الفردية",
2678
+ "SEOPage.tab.plugin-title": "الإضافات",
2679
+ "SEOPage.tab-panel.column-name": "الاسم",
2680
+ "SEOSocialPreview.website-url": "url-of-your-website.io",
2681
+ "SEOSocialPreview.keyword-check": "أنت تستخدم {count} كلمة (كلمات) مفتاحية في {label} ({matches})",
2682
+ "SEOSocialPreview.meta-length-check": "طول {label} هو {length} حرفاً (الحد الأقصى {max})",
2683
+ "SEOModal.summary-title.meta-title": "العنوان الوصفي (Meta Title)",
2684
+ "SEOModal.summary-title.meta-description": "الوصف التعريفي (Meta Description)",
2685
+ "SEOModal.summary-title.word-counter": "عداد الكلمات",
2686
+ "SEOModal.summary-title.word-densisty": "كثافة الكلمات المفتاحية",
2687
+ "SEOModal.summary-title.open-graph": "وسوم Open Graph",
2688
+ "SEOModal.summary-title.canonical-url": "الرابط الأساسي (Canonical URL)",
2689
+ "SEOModal.summary-title.structured-data": "بيانات JSON المنظمة",
2690
+ "SEOModal.summary-title.meta-robots": "وسوم الروبوتات (Meta Robots)",
2691
+ "SEOModal.summary-title.alt-attributes": "النصوص البديلة للصور",
2692
+ "SEOModal.summary-title.last-updated-at": "آخر تحديث في",
2693
+ "SEOChecks.keywordsDensityCheck.default": "يتم استخدام كل كلمة مفتاحية أكثر من مرتين.",
2694
+ "SEOChecks.keywordsDensityCheck.no-keywords": "لم يتم العثور على كلمات مفتاحية في مكوّن SEO الخاص بك.",
2695
+ "SEOChecks.keywordsDensityCheck.one-not-used": "كلمة مفتاحية واحدة على الأقل غير مستخدمة في محتواك.",
2696
+ "SEOChecks.keywordsDensityCheck.one-used-once": "كلمة مفتاحية واحدة مستخدمة مرة واحدة فقط في محتواك.",
2697
+ "SEOChecks.keywordsDensityCheck.label": "حدد الكلمات المفتاحية التي تريد التصدر بها في مكوّن SEO. سيقوم هذا بتحليل كثافة الكلمات المفتاحية في حقول النص المنسق.",
2698
+ "SEOChecks.alternativeTextCheck.default": "جميع صورك تحتوي على نص بديل (alt)! تهانينا!",
2699
+ "SEOChecks.alternativeTextCheck.intersection-zero": "اسم الصور والنص البديل متطابقان. يقوم Strapi تلقائياً بتوليد سمة alt من اسم الملف إذا تُرك الحقل فارغاً أثناء الرفع. كتابة نص بديل يصف الصورة بوضوح ممارسة ممتازة.",
2700
+ "SEOChecks.alternativeTextCheck.intersection-negative": "صورة (أو صور) في حقل الوسائط تفتقر إلى سمة النص البديل alt.",
2701
+ "SEOChecks.alternativeTextCheck.richtext-missing-one": "صورة واحدة على الأقل في محرر النص المنسق تفتقر إلى سمة alt.",
2702
+ "SEOChecks.alternativeTextCheck.label": "سيتحقق هذا مما إذا كان هناك أي نص بديل مفقود لصورك في حقول الوسائط ومحررات النص المنسق.",
2703
+ "SEOChecks.alternativeTextCheck.missing-text": "نص بديل مفقود في حقل النص المنسق التالي:",
2704
+ "SEOChecks.alternativeTextCheck.missing-image": "مفقود في حقول الصور الأساسية أو داخل مكوّن.",
2705
+ "SEOChecks.alternativeTextCheck.tip-label": "نصيحة:",
2706
+ "SEOChecks.alternativeTextCheck.tip-before": "طبّق قاعدة في كود الواجهة الأمامية تستخدم اسم الصور إذا كان حقل",
2707
+ "SEOChecks.alternativeTextCheck.tip-after": "فارغاً لتقليل خطر وجود سمة alt فارغة في موقعك.",
2708
+ "SEOChecks.canonicalUrlCheck.default": "لم يتم العثور على رابط أساسي (Canonical URL).",
2709
+ "SEOChecks.canonicalUrlCheck.found": "تم العثور على رابط أساسي (Canonical URL)!",
2710
+ "SEOChecks.canonicalUrlCheck.label": "سيتحقق هذا مما إذا كان لديك رابط أساسي (Canonical URL).",
2711
+ "SEOChecks.canonicalUrlCheck.url": "الرابط الأساسي:",
2712
+ "SEOChecks.lastUpdatedAtCheck.default": "تم تعديل هذا المحتوى منذ أكثر من عام! محركات البحث تفضل المحتوى المتجدد.",
2713
+ "SEOChecks.lastUpdatedAtCheck.save-content": "يجب حفظ هذا السجل أولاً.",
2714
+ "SEOChecks.lastUpdatedAtCheck.success": "رائع! تم تعديل هذا المحتوى خلال أقل من عام!",
2715
+ "SEOChecks.lastUpdatedAtCheck.label": "محركات البحث تحب المحتوى المتجدد. سيتحقق هذا مما إذا تم تعديل السجل خلال أقل من عام.",
2716
+ "SEOChecks.lastUpdatedAtCheck.last": "آخر تحديث في:",
2717
+ "SEOChecks.metaDescriptionCheck.default": "تم العثور على الوصف التعريفي (Meta Description)!",
2718
+ "SEOChecks.metaDescriptionCheck.not-found": "لم يتم العثور على الوصف التعريفي (Meta Description).",
2719
+ "SEOChecks.metaRobotsCheck.default": "تم العثور على وسوم الروبوتات (Meta Robots)!",
2720
+ "SEOChecks.metaRobotsCheck.not-found": "لم يتم العثور على وسوم الروبوتات (Meta Robots).",
2721
+ "SEOChecks.metaRobotsCheck.label": "يُعلم وسم الروبوتات محركات البحث بالصفحات التي يجب فهرستها في موقعك والمزيد.",
2722
+ "SEOChecks.metaRobotsCheck.activated": "{tag} مفعّل: {message}",
2723
+ "SEOChecks.metaRobotsCheck.disabled": "{tag} معطّل: {message}",
2724
+ "SEOChecks.metaRobotsCheck.notice-label": "ملاحظة:",
2725
+ "SEOChecks.metaRobotsCheck.notice-before": "لعدم فهرسة هذا السجل وعدم تتبع روابطه، يجب أن يحتوي حقل MetaRobots على التالي:",
2726
+ "SEOChecks.metaRobotsCheck.notice-value": "noindex, nofollow",
2727
+ "SEOChecks.metaRobotsCheck.notice-after": ". يجب التعامل مع الباقي بواسطة منطق كود الواجهة الأمامية لديك.",
2728
+ "SEOChecks.openGraphCheck.not-found": "لم يتم العثور على وسوم Open Graph.",
2729
+ "SEOChecks.openGraphCheck.not-configured": "الحد الأدنى من وسوم OpenGraph المطلوبة غير مُعد.",
2730
+ "SEOChecks.openGraphCheck.configured": "تم إعداد وسوم Open Graph",
2731
+ "SEOChecks.openGraphCheck.label": "تتيح لك وسوم Open Graph إدارة العنوان والوصف والصورة الخاصة بمنشوراتك عند مشاركتها.",
2732
+ "SEOChecks.metaTitleCheck.default": "تم العثور على العنوان الوصفي (Meta Title)!",
2733
+ "SEOChecks.metaTitleCheck.not-found": "لم يتم العثور على العنوان الوصفي (Meta Title).",
2734
+ "SEOChecks.structuredDataCheck.default": "تم العثور على بيانات JSON المنظمة! ومع ذلك، لا يمكننا التحقق التلقائي من دقة محتواها.",
2735
+ "SEOChecks.structuredDataCheck.not-found": "لم يتم العثور على بيانات JSON المنظمة.",
2736
+ "SEOChecks.structuredDataCheck.label": "البيانات المنظمة هي تنسيق قياسي لتوفير معلومات حول الصفحة وتصنيف محتواها لمحركات البحث.",
2737
+ "SEOChecks.wordCountCheck.default": "لديك أكثر من 300 كلمة. عدد الكلمات ليس عامل ترتيب مباشر، ولكن يجب أن يكون محتواك عالي الجودة ومفصلاً.",
2738
+ "SEOChecks.wordCountCheck.not-found": "لم يتم العثور على محتوى نص منسق.",
2739
+ "SEOChecks.wordCountCheck.300": "يحتوي النص المنسق في المستوى الأول على أقل من 300 كلمة.",
2740
+ "SEOChecks.wordCountCheck.label": "يجب أن يكون محتواك بأعلى جودة ممكنة مع معلومات مفيدة وفريدة، مما يتطلب حداً أدنى من الفقرات والكلمات.",
2741
+ "SEOChecks.wordCountCheck.words": "الكلمات:",
2742
+ "Modal.seo-component-empty": "مكوّن SEO الخاص بك فارغ...",
2743
+ "Modal.seo-component-empty-open-graph": "بيانات Open Graph الوصفية فارغة...",
2744
+ "Tabs.title-settings": "إعدادات العنوان",
2745
+ "Tabs.social-network": "الشبكات الاجتماعية",
2746
+ "Tabs.seo-analyse": "تحليل SEO",
2747
+ "Title-settings.metaTitle-too-long": "العنوان الوصفي طويل جداً",
2748
+ "Title-settings.metaTitle-tooltip": "وسم العنوان هو أول ما يلاحظه المستخدمون في نتائج البحث، ويقدم معاينة لمحتوى صفحتك.",
2749
+ "Title-settings.metaDescription-too-long": "الوصف التعريفي طويل جداً",
2750
+ "Title-settings.metaDescription-too-short": "الوصف التعريفي قصير جداً",
2751
+ "Title-settings.metaDescription-tooltip": "الوصف التعريفي لا يقل أهمية عن العنوان، فهو المقتطف الذي يظهر أسفل العنوان في نتائج البحث.",
2752
+ "Title-settings.metaTitle-hint": "/60 حرفاً (الحد الأقصى الموصى به)",
2753
+ "Title-settings.metaDescription-hint": "/160 حرفاً (الحد الأقصى الموصى به)",
2754
+ "Social-network.metaTitle-hint": "/60 حرفاً (الحد الأقصى الموصى به)",
2755
+ "Social-network.metaDescription-hint": "/65 حرفاً (الحد الأقصى الموصى به)",
2756
+ "Social-network.accordion.description": "شاهد كيف سيبدو محتواك عند نشره على",
2757
+ "Social-network.no-tags": "ليس لديك أي وسوم Open Graph حتى الآن.",
2758
+ "Social-network.platform.facebook": "فيسبوك (Facebook)",
2759
+ "Social-network.platform.twitter": "إكس (X / Twitter)",
2760
+ "Social-network.platform.linkedin": "لينكدإن (LinkedIn)",
2761
+ "Modal.cancel": "إلغاء",
2762
+ "Button.see-details": "عرض التفاصيل",
2763
+ "Button.open-graph-preview": "معاينة Open Graph",
2764
+ "Button.browser-preview": "معاينة المتصفح",
2765
+ "Button.seo-analyze": "تحليل SEO",
2766
+ Good: Good$5,
2767
+ Improvements: Improvements$5,
2768
+ Bad: Bad$5,
2769
+ "Social-preview.alert": "أكمل مكوّن الشبكات الاجتماعية لرؤية المعاينة",
2770
+ Information: Information$5,
2771
+ "notification.success.settings": "تم حفظ الإعدادات لنوع المحتوى {collectionName}.",
2772
+ "notification.error.settings": "فشل في حفظ إعدادات SEO."
2773
+ };
2774
+ const Good$4 = "Good";
2775
+ const Improvements$4 = "Improvements";
2776
+ const Bad$4 = "Bad";
2777
+ const Information$4 = "Information";
2778
+ const en = {
2779
+ "Plugin.name": "SEO Plugin",
2780
+ "SEOPage.header.button.edit-component": "Edit SEO component",
2781
+ "SEOPage.header.button.back": "Back",
2782
+ "SEOPage.header.title": "SEO",
2783
+ "SEOPage.header.subtitle": "Optimize your content to be SEO friendly",
2784
+ "SEOPage.empty-component-layout.github-error": "This GitHub repository doesn't contains shared.seo or shared.open-graph component",
2785
+ "SEOPage.empty-component-layout.regexp-error": "Please provide a valid GitHub repository URL",
2786
+ "SEOPage.empty-component-layout.no-component": "You don't have any SEO component yet",
2787
+ "SEOPage.empty-component-layout.input.tooltip": "The strapi/components GitHub repository contains the default SEO component",
2788
+ "SEOPage.info.information": "Make sure to use the imported shared.seo component, name it 'seo' and to add it in the root of your Content-Type.",
2789
+ "SEOPage.info.no-collection-types": "You don't have any collection-types yet...",
2790
+ "SEOPage.info.no-single-types": "You don't have any single-types yet...",
2791
+ "SEOPage.info.no-plugin-content-type": "You don't have any plugin content-type yet...",
2792
+ "SEOPage.info.create-collection-type": "Create your first collection-type",
2793
+ "SEOPage.info.create-single-type": "Create your first single-type",
2794
+ "SEOPage.info.added": "Added",
2795
+ "SEOPage.info.add": "Add component",
2796
+ "SEOPage.info.settings": "Settings",
2797
+ "SEOPage.info.settings.meta-title-check": "Meta Title",
2798
+ "SEOPage.info.settings.meta-description-check": "Meta Description",
2799
+ "SEOPage.info.settings.meta-robots-check": "Meta Robots",
2800
+ "SEOPage.info.settings.open-graph-check": "Open Graph",
2801
+ "SEOPage.info.settings.word-count-check": "Word Count",
2802
+ "SEOPage.info.settings.canonical-url-check": "Canonical URL",
2803
+ "SEOPage.info.settings.keyword-density-check": "Keyword Density",
2804
+ "SEOPage.info.settings.structured-data-check": "Structured Data",
2805
+ "SEOPage.info.settings.alternative-text-check": "Alt Text",
2806
+ "SEOPage.info.settings.last-updated-at-check": "Last Updated At",
2807
+ "SEOPage.info.settings.cancel.button": "Cancel",
2808
+ "SEOPage.info.settings.save.button": "Save",
2809
+ "SEOPage.info.settings.information": "Disable SEO checks for this specific content-type.",
2810
+ "SEOPage.tab.collection-type-title": "Collection Types",
2811
+ "SEOPage.tab.single-type-title": "Single Types",
2812
+ "SEOPage.tab.plugin-title": "Plugins",
2813
+ "SEOPage.tab-panel.column-name": "Name",
2814
+ "SEOSocialPreview.website-url": "url-of-your-website.io",
2815
+ "SEOSocialPreview.keyword-check": "You use {count} keyword(s) in your {label} ({matches})",
2816
+ "SEOSocialPreview.meta-length-check": "Your {label} is {length} characters long (max {max})",
2817
+ "SEOModal.summary-title.meta-title": "Meta Title",
2818
+ "SEOModal.summary-title.meta-description": "Meta Description",
2819
+ "SEOModal.summary-title.word-counter": "Word Counter",
2820
+ "SEOModal.summary-title.word-densisty": "Keyword Density",
2821
+ "SEOModal.summary-title.open-graph": "Open Graph Tags",
2822
+ "SEOModal.summary-title.canonical-url": "Canonical URL",
2823
+ "SEOModal.summary-title.structured-data": "JSON Structured Data",
2824
+ "SEOModal.summary-title.meta-robots": "Meta Robots",
2825
+ "SEOModal.summary-title.alt-attributes": "Alternative Texts",
2826
+ "SEOModal.summary-title.last-updated-at": "Last updated at",
2827
+ "SEOChecks.keywordsDensityCheck.default": "Every keyword are used more than 2 times.",
2828
+ "SEOChecks.keywordsDensityCheck.no-keywords": "No keywords were found in your SEO component.",
2829
+ "SEOChecks.keywordsDensityCheck.one-not-used": "One keyword is not being used in your content.",
2830
+ "SEOChecks.keywordsDensityCheck.one-used-once": "One keyword is only used once in your content.",
2831
+ "SEOChecks.keywordsDensityCheck.label": "Define keywords you want to rank for in your SEO component. This will analyze the density of your keywords in your 1st level richtext editors.",
2832
+ "SEOChecks.alternativeTextCheck.default": "All your images contain an alt attribute! Congrats!",
2833
+ "SEOChecks.alternativeTextCheck.intersection-zero": "The name and the alternative text of your images are all the same. Strapi automatically generates the alt attribute from the name by default if the field was empty during the media upload. Having alt attribute clearly describing images is a very good practice.",
2834
+ "SEOChecks.alternativeTextCheck.intersection-negative": "image(s) from a media field is/are missing an alt attribute.",
2835
+ "SEOChecks.alternativeTextCheck.richtext-missing-one": "At least one image in any 1st level richtext editor is missing an alt attribute.",
2836
+ "SEOChecks.alternativeTextCheck.label": "This will check if you have any missing alternative text for your images (media field) and in your 1st level richtext editors.",
2837
+ "SEOChecks.alternativeTextCheck.missing-text": "missing alt in the following richtext field:",
2838
+ "SEOChecks.alternativeTextCheck.missing-image": "missing on first level image fields or inside a component.",
2839
+ "SEOChecks.alternativeTextCheck.tip-label": "Tip:",
2840
+ "SEOChecks.alternativeTextCheck.tip-before": "Implement a rule in your front-end code that uses the name of your images if the",
2841
+ "SEOChecks.alternativeTextCheck.tip-after": "field is empty to further reduce the risk of an empty alt attribute on your website.",
2842
+ "SEOChecks.canonicalUrlCheck.default": "No canonical URL has been found.",
2843
+ "SEOChecks.canonicalUrlCheck.found": "A Canonical URL has been found!",
2844
+ "SEOChecks.canonicalUrlCheck.label": "This will check if you have a canonical URL.",
2845
+ "SEOChecks.canonicalUrlCheck.url": "Canonical URL:",
2846
+ "SEOChecks.lastUpdatedAtCheck.default": "This content was modified over a year ago! Search engines love fresh content.",
2847
+ "SEOChecks.lastUpdatedAtCheck.save-content": "You must save this entry first.",
2848
+ "SEOChecks.lastUpdatedAtCheck.success": "Awesome! This content was last modified less than a year ago!",
2849
+ "SEOChecks.lastUpdatedAtCheck.label": "Search engines love fresh content. This will check if your entry was last modified less than a year ago.",
2850
+ "SEOChecks.lastUpdatedAtCheck.last": "Last updated at:",
2851
+ "SEOChecks.metaDescriptionCheck.default": "A Meta Description has been found!",
2852
+ "SEOChecks.metaDescriptionCheck.not-found": "No Meta Description has been found.",
2853
+ "SEOChecks.metaRobotsCheck.default": "Robot meta tags have been found!",
2854
+ "SEOChecks.metaRobotsCheck.not-found": "No Robot meta tags have been found.",
2855
+ "SEOChecks.metaRobotsCheck.label": "The robot's meta tag informs search engines which pages on your site should be indexed and more.",
2856
+ "SEOChecks.metaRobotsCheck.activated": "{tag} is activated: {message}",
2857
+ "SEOChecks.metaRobotsCheck.disabled": "{tag} is disabled: {message}",
2858
+ "SEOChecks.metaRobotsCheck.notice-label": "Notice:",
2859
+ "SEOChecks.metaRobotsCheck.notice-before": "In order to not index your entry and no follow, your MetaRobots field should contain the following:",
2860
+ "SEOChecks.metaRobotsCheck.notice-value": "noindex, nofollow",
2861
+ "SEOChecks.metaRobotsCheck.notice-after": '. The rest should be handled by your front-end code logic. "If the field contains noindex, then you need to create the corresponding meta tag etc..."',
2862
+ "SEOChecks.openGraphCheck.not-found": "No Open Graph tags have been found.",
2863
+ "SEOChecks.openGraphCheck.not-configured": "OpenGraph minimum required tags are not configured.",
2864
+ "SEOChecks.openGraphCheck.configured": "Open Graph tags are configured",
2865
+ "SEOChecks.openGraphCheck.label": "Open Graph tags allow you to manage the title, description & image of your posts.",
2866
+ "SEOChecks.metaTitleCheck.default": "A Meta Title has been found!",
2867
+ "SEOChecks.metaTitleCheck.not-found": "No Meta Title has been found.",
2868
+ "SEOChecks.structuredDataCheck.default": "A Structured Data JSON has been found! However, we can't validate the accuracy of its content.",
2869
+ "SEOChecks.structuredDataCheck.not-found": "No Structured Data JSON has been found.",
2870
+ "SEOChecks.structuredDataCheck.label": "Structured data is a standardized format for providing information about a page and classifying the page content.",
2871
+ "SEOChecks.wordCountCheck.default": "You have more than 300 words. The word count is not a direct ranking factor. But your content should be as high quality as possible, with relevant and unique information. To meet these conditions, your content requires a minimum of paragraphs, and therefore of words.",
2872
+ "SEOChecks.wordCountCheck.not-found": "No Richtext content have been found.",
2873
+ "SEOChecks.wordCountCheck.300": "Your 1st level Richtext contents have less than 300 words.",
2874
+ "SEOChecks.wordCountCheck.label": "Your content should be as high quality as possible, with relevant and unique information. Your entry requires a minimum of paragraphs, and therefore of words.",
2875
+ "SEOChecks.wordCountCheck.words": "Words:",
2876
+ "Modal.seo-component-empty": "Your SEO component is empty...",
2877
+ "Modal.seo-component-empty-open-graph": "The Open Graph metadata is empty...",
2878
+ "Tabs.title-settings": "Title settings",
2879
+ "Tabs.social-network": "Social networks",
2880
+ "Tabs.seo-analyse": "SEO analyze",
2881
+ "Title-settings.metaTitle-too-long": "Meta Title is too long",
2882
+ "Title-settings.metaTitle-tooltip": "The title tag is one of the first things that users notice in the SERPs. It’s the title of your page that offers a preview of what your content is about.",
2883
+ "Title-settings.metaDescription-too-long": "Meta Description is too long",
2884
+ "Title-settings.metaDescription-too-short": "Meta Description is too short",
2885
+ "Title-settings.metaDescription-tooltip": "The meta description is of equal importance to the title tag. If the title tag is the title that appears at the top of a search result, the meta description is the snippet that displays underneath.",
2886
+ "Title-settings.metaTitle-hint": "/60 characters (recommended maximum limit)",
2887
+ "Title-settings.metaDescription-hint": "/160 characters (recommended maximum limit)",
2888
+ "Social-network.metaTitle-hint": "/60 characters (recommended maximum limit)",
2889
+ "Social-network.metaDescription-hint": "/65 characters (recommended maximum limit)",
2890
+ "Social-network.accordion.description": "See how your content will look like posted on",
2891
+ "Social-network.no-tags": "You don't have any Open Graph tags yet.",
2892
+ "Social-network.platform.facebook": "Facebook",
2893
+ "Social-network.platform.twitter": "X (Twitter)",
2894
+ "Social-network.platform.linkedin": "LinkedIn",
2895
+ "Modal.cancel": "Cancel",
2896
+ "Button.see-details": "SEE DETAILS",
2897
+ "Button.open-graph-preview": "Open Graph Preview",
2898
+ "Button.browser-preview": "Browser Preview",
2899
+ "Button.seo-analyze": "SEO Analyze",
2900
+ Good: Good$4,
2901
+ Improvements: Improvements$4,
2902
+ Bad: Bad$4,
2903
+ "Social-preview.alert": "Complete your social component to see the preview",
2904
+ Information: Information$4,
2905
+ "notification.success.settings": "Settings saved for {collectionName} content-type.",
2906
+ "notification.error.settings": "Failed to save SEO settings."
2907
+ };
2908
+ const Good$3 = "Bueno";
2909
+ const Improvements$3 = "Mejoras";
2910
+ const Bad$3 = "Malo";
2911
+ const Information$3 = "Información";
2912
+ const es = {
2913
+ "Plugin.name": "Complemento SEO",
2914
+ "SEOPage.header.button.edit-component": "Editar componente SEO",
2915
+ "SEOPage.header.button.back": "Volver",
2916
+ "SEOPage.header.title": "SEO",
2917
+ "SEOPage.header.subtitle": "Optimiza tu contenido para motores de búsqueda (SEO)",
2918
+ "SEOPage.empty-component-layout.github-error": "Este repositorio de GitHub no contiene el componente shared.seo o shared.open-graph",
2919
+ "SEOPage.empty-component-layout.regexp-error": "Por favor proporciona una URL válida de repositorio de GitHub",
2920
+ "SEOPage.empty-component-layout.no-component": "Aún no tienes ningún componente SEO",
2921
+ "SEOPage.empty-component-layout.input.tooltip": "El repositorio de GitHub strapi/components contiene el componente SEO predeterminado",
2922
+ "SEOPage.info.information": "Asegúrate de usar el componente shared.seo importado, nombrarlo 'seo' y añadirlo en la raíz de tu tipo de contenido.",
2923
+ "SEOPage.info.no-collection-types": "Aún no tienes ningún tipo de colección...",
2924
+ "SEOPage.info.no-single-types": "Aún no tienes ningún tipo único...",
2925
+ "SEOPage.info.no-plugin-content-type": "Aún no tienes ningún tipo de contenido de plugin...",
2926
+ "SEOPage.info.create-collection-type": "Crea tu primer tipo de colección",
2927
+ "SEOPage.info.create-single-type": "Crea tu primer tipo único",
2928
+ "SEOPage.info.added": "Añadido",
2929
+ "SEOPage.info.add": "Añadir componente",
2930
+ "SEOPage.info.settings": "Configuración",
2931
+ "SEOPage.info.settings.meta-title-check": "Título Meta",
2932
+ "SEOPage.info.settings.meta-description-check": "Descripción Meta",
2933
+ "SEOPage.info.settings.meta-robots-check": "Meta Robots",
2934
+ "SEOPage.info.settings.open-graph-check": "Open Graph",
2935
+ "SEOPage.info.settings.word-count-check": "Recuento de palabras",
2936
+ "SEOPage.info.settings.canonical-url-check": "URL Canónica",
2937
+ "SEOPage.info.settings.keyword-density-check": "Densidad de palabras clave",
2938
+ "SEOPage.info.settings.structured-data-check": "Datos estructurados",
2939
+ "SEOPage.info.settings.alternative-text-check": "Texto alternativo (Alt)",
2940
+ "SEOPage.info.settings.last-updated-at-check": "Última actualización",
2941
+ "SEOPage.info.settings.cancel.button": "Cancelar",
2942
+ "SEOPage.info.settings.save.button": "Guardar",
2943
+ "SEOPage.info.settings.information": "Desactivar comprobaciones SEO para este tipo de contenido específico.",
2944
+ "SEOPage.tab.collection-type-title": "Tipos de Colección",
2945
+ "SEOPage.tab.single-type-title": "Tipos Únicos",
2946
+ "SEOPage.tab.plugin-title": "Plugins",
2947
+ "SEOPage.tab-panel.column-name": "Nombre",
2948
+ "SEOSocialPreview.website-url": "url-de-tu-sitio-web.org",
2949
+ "SEOSocialPreview.keyword-check": "Usas {count} palabra(s) clave en tu {label} ({matches})",
2950
+ "SEOSocialPreview.meta-length-check": "Tu {label} tiene {length} caracteres (máx. {max})",
2951
+ "SEOModal.summary-title.meta-title": "Título Meta",
2952
+ "SEOModal.summary-title.meta-description": "Descripción Meta",
2953
+ "SEOModal.summary-title.word-counter": "Contador de palabras",
2954
+ "SEOModal.summary-title.word-densisty": "Densidad de palabras clave",
2955
+ "SEOModal.summary-title.open-graph": "Etiquetas Open Graph",
2956
+ "SEOModal.summary-title.canonical-url": "URL Canónica",
2957
+ "SEOModal.summary-title.structured-data": "Datos Estructurados JSON",
2958
+ "SEOModal.summary-title.meta-robots": "Meta Robots",
2959
+ "SEOModal.summary-title.alt-attributes": "Textos alternativos",
2960
+ "SEOModal.summary-title.last-updated-at": "Última actualización",
2961
+ "SEOChecks.keywordsDensityCheck.default": "Cada palabra clave se utiliza más de 2 veces.",
2962
+ "SEOChecks.keywordsDensityCheck.no-keywords": "No se encontraron palabras clave en tu componente SEO.",
2963
+ "SEOChecks.keywordsDensityCheck.one-not-used": "Una palabra clave no se está utilizando en tu contenido.",
2964
+ "SEOChecks.keywordsDensityCheck.one-used-once": "Una palabra clave solo se utiliza una vez en tu contenido.",
2965
+ "SEOChecks.keywordsDensityCheck.label": "Define las palabras clave por las que deseas posicionar en tu componente SEO. Esto analizará la densidad de tus palabras clave en los editores de texto enriquecido de primer nivel.",
2966
+ "SEOChecks.alternativeTextCheck.default": "¡Todas tus imágenes contienen un atributo alt! ¡Felicidades!",
2967
+ "SEOChecks.alternativeTextCheck.intersection-zero": "El nombre y el texto alternativo de tus imágenes son iguales. Strapi genera automáticamente el atributo alt a partir del nombre por defecto si el campo estaba vacío al subir el archivo. Tener un atributo alt que describa claramente las imágenes es una excelente práctica.",
2968
+ "SEOChecks.alternativeTextCheck.intersection-negative": "imagen(es) de un campo multimedia no tienen atributo alt.",
2969
+ "SEOChecks.alternativeTextCheck.richtext-missing-one": "Al menos una imagen en algún editor de texto enriquecido de primer nivel no tiene atributo alt.",
2970
+ "SEOChecks.alternativeTextCheck.label": "Esto comprobará si falta texto alternativo en tus imágenes (campo multimedia) y en tus editores de texto enriquecido de primer nivel.",
2971
+ "SEOChecks.alternativeTextCheck.missing-text": "falta alt en el siguiente campo de texto enriquecido:",
2972
+ "SEOChecks.alternativeTextCheck.missing-image": "falta en campos de imagen de primer nivel o dentro de un componente.",
2973
+ "SEOChecks.alternativeTextCheck.tip-label": "Consejo:",
2974
+ "SEOChecks.alternativeTextCheck.tip-before": "Implementa una regla en tu código front-end que utilice el nombre de tus imágenes si el campo",
2975
+ "SEOChecks.alternativeTextCheck.tip-after": "está vacío para reducir aún más el riesgo de tener un atributo alt vacío en tu sitio web.",
2976
+ "SEOChecks.canonicalUrlCheck.default": "No se ha encontrado ninguna URL canónica.",
2977
+ "SEOChecks.canonicalUrlCheck.found": "¡Se ha encontrado una URL canónica!",
2978
+ "SEOChecks.canonicalUrlCheck.label": "Esto comprobará si tienes una URL canónica configurada.",
2979
+ "SEOChecks.canonicalUrlCheck.url": "URL Canónica:",
2980
+ "SEOChecks.lastUpdatedAtCheck.default": "¡Este contenido fue modificado hace más de un año! A los motores de búsqueda les encanta el contenido fresco.",
2981
+ "SEOChecks.lastUpdatedAtCheck.save-content": "Primero debes guardar esta entrada.",
2982
+ "SEOChecks.lastUpdatedAtCheck.success": "¡Excelente! ¡Este contenido fue modificado hace menos de un año!",
2983
+ "SEOChecks.lastUpdatedAtCheck.label": "A los motores de búsqueda les encanta el contenido actualizado. Esto comprobará si tu entrada fue modificada hace menos de un año.",
2984
+ "SEOChecks.lastUpdatedAtCheck.last": "Última actualización:",
2985
+ "SEOChecks.metaDescriptionCheck.default": "¡Se ha encontrado una Descripción Meta!",
2986
+ "SEOChecks.metaDescriptionCheck.not-found": "No se ha encontrado ninguna Descripción Meta.",
2987
+ "SEOChecks.metaRobotsCheck.default": "¡Se han encontrado etiquetas Meta Robots!",
2988
+ "SEOChecks.metaRobotsCheck.not-found": "No se han encontrado etiquetas Meta Robots.",
2989
+ "SEOChecks.metaRobotsCheck.label": "La etiqueta meta robots informa a los motores de búsqueda qué páginas de tu sitio deben indexarse y más.",
2990
+ "SEOChecks.metaRobotsCheck.activated": "{tag} está activado: {message}",
2991
+ "SEOChecks.metaRobotsCheck.disabled": "{tag} está desactivado: {message}",
2992
+ "SEOChecks.metaRobotsCheck.notice-label": "Aviso:",
2993
+ "SEOChecks.metaRobotsCheck.notice-before": "Para no indexar tu entrada ni seguir enlaces, tu campo MetaRobots debe contener lo siguiente:",
2994
+ "SEOChecks.metaRobotsCheck.notice-value": "noindex, nofollow",
2995
+ "SEOChecks.metaRobotsCheck.notice-after": '. El resto debe manejarse en la lógica de tu código front-end. "Si el campo contiene noindex, debes crear la etiqueta meta correspondiente, etc."',
2996
+ "SEOChecks.openGraphCheck.not-found": "No se han encontrado etiquetas Open Graph.",
2997
+ "SEOChecks.openGraphCheck.not-configured": "Las etiquetas mínimas requeridas de OpenGraph no están configuradas.",
2998
+ "SEOChecks.openGraphCheck.configured": "Las etiquetas Open Graph están configuradas",
2999
+ "SEOChecks.openGraphCheck.label": "Las etiquetas Open Graph te permiten gestionar el título, la descripción y la imagen de tus publicaciones en redes sociales.",
3000
+ "SEOChecks.metaTitleCheck.default": "¡Se ha encontrado un Título Meta!",
3001
+ "SEOChecks.metaTitleCheck.not-found": "No se ha encontrado ningún Título Meta.",
3002
+ "SEOChecks.structuredDataCheck.default": "¡Se ha encontrado un JSON de Datos Estructurados! Sin embargo, no podemos validar la exactitud de su contenido.",
3003
+ "SEOChecks.structuredDataCheck.not-found": "No se ha encontrado ningún JSON de Datos Estructurados.",
3004
+ "SEOChecks.structuredDataCheck.label": "Los datos estructurados son un formato estandarizado para proporcionar información sobre una página y clasificar su contenido.",
3005
+ "SEOChecks.wordCountCheck.default": "Tienes más de 300 palabras. El recuento de palabras no es un factor directo de posicionamiento, pero tu contenido debe ser de la más alta calidad posible, con información relevante y única.",
3006
+ "SEOChecks.wordCountCheck.not-found": "No se ha encontrado contenido de texto enriquecido.",
3007
+ "SEOChecks.wordCountCheck.300": "Tu contenido de texto enriquecido de primer nivel tiene menos de 300 palabras.",
3008
+ "SEOChecks.wordCountCheck.label": "Tu contenido debe ser de la más alta calidad posible, con información relevante y única. Tu entrada requiere un mínimo de párrafos y palabras.",
3009
+ "SEOChecks.wordCountCheck.words": "Palabras:",
3010
+ "Modal.seo-component-empty": "Tu componente SEO está vacío...",
3011
+ "Modal.seo-component-empty-open-graph": "Los metadatos de Open Graph están vacíos...",
3012
+ "Tabs.title-settings": "Ajustes de título",
3013
+ "Tabs.social-network": "Redes sociales",
3014
+ "Tabs.seo-analyse": "Análisis SEO",
3015
+ "Title-settings.metaTitle-too-long": "El Título Meta es demasiado largo",
3016
+ "Title-settings.metaTitle-tooltip": "La etiqueta de título es una de las primeras cosas que los usuarios notan en los resultados de búsqueda. Ofrece una vista previa de qué trata tu contenido.",
3017
+ "Title-settings.metaDescription-too-long": "La Descripción Meta es demasiado larga",
3018
+ "Title-settings.metaDescription-too-short": "La Descripción Meta es demasiado corta",
3019
+ "Title-settings.metaDescription-tooltip": "La descripción meta tiene la misma importancia que la etiqueta de título. Es el fragmento descriptivo que se muestra debajo del título en los resultados de búsqueda.",
3020
+ "Title-settings.metaTitle-hint": "/60 caracteres (límite máximo recomendado)",
3021
+ "Title-settings.metaDescription-hint": "/160 caracteres (límite máximo recomendado)",
3022
+ "Social-network.metaTitle-hint": "/60 caracteres (límite máximo recomendado)",
3023
+ "Social-network.metaDescription-hint": "/65 caracteres (límite máximo recomendado)",
3024
+ "Social-network.accordion.description": "Mira cómo se verá tu contenido publicado en",
3025
+ "Social-network.no-tags": "Aún no tienes ninguna etiqueta Open Graph.",
3026
+ "Social-network.platform.facebook": "Facebook",
3027
+ "Social-network.platform.twitter": "X (Twitter)",
3028
+ "Social-network.platform.linkedin": "LinkedIn",
3029
+ "Modal.cancel": "Cancelar",
3030
+ "Button.see-details": "VER DETALLES",
3031
+ "Button.open-graph-preview": "Vista previa Open Graph",
3032
+ "Button.browser-preview": "Vista previa del navegador",
3033
+ "Button.seo-analyze": "Análisis SEO",
3034
+ Good: Good$3,
3035
+ Improvements: Improvements$3,
3036
+ Bad: Bad$3,
3037
+ "Social-preview.alert": "Completa tu componente social para ver la vista previa",
3038
+ Information: Information$3,
3039
+ "notification.success.settings": "Configuración guardada para el tipo de contenido {collectionName}.",
3040
+ "notification.error.settings": "No se pudo guardar la configuración SEO."
3041
+ };
3042
+ const Good$2 = "Bien";
3043
+ const Improvements$2 = "Améliorations";
3044
+ const Bad$2 = "Problèmes";
3045
+ const Information$2 = "Information";
3046
+ const fr = {
3047
+ "Plugin.name": "Plugin SEO",
3048
+ "SEOPage.header.button.edit-component": "Editer le composant SEO",
3049
+ "SEOPage.header.button.back": "Retour",
3050
+ "SEOPage.header.title": "SEO",
3051
+ "SEOPage.header.subtitle": "Optimisez votre contenu pour le référencement",
3052
+ "SEOPage.empty-component-layout.github-error": "Cette repository GitHub ne contient pas de composant shared.seo ou shared.open-graph",
3053
+ "SEOPage.empty-component-layout.regexp-error": "Veuillez fournir une URL de repository GitHub valide",
3054
+ "SEOPage.empty-component-layout.no-component": "Vous n'avez pas encore de composant SEO",
3055
+ "SEOPage.empty-component-layout.input.tooltip": "Le repository GitHub strapi/components contient le composant SEO par défaut",
3056
+ "SEOPage.info.information": "Assurez-vous d'utiliser le composant importé shared.seo, de le nommer 'seo' et de l'inclure à la racine de votre Content-Type.",
3057
+ "SEOPage.info.no-collection-types": "Vous n'avez pas encore de collection-types...",
3058
+ "SEOPage.info.no-single-types": "Vous n'avez pas enore de single-types...",
3059
+ "SEOPage.info.no-plugin-content-type": "Vous n'avez pas encore de content-type de plugin...",
3060
+ "SEOPage.info.create-collection-type": "Créer votre premier collection-type",
3061
+ "SEOPage.info.create-single-type": "Créer votre premier single-type",
3062
+ "SEOPage.info.added": "Ajouté",
3063
+ "SEOPage.info.add": "Ajouter le composant",
3064
+ "SEOPage.info.settings": "Paramètres",
3065
+ "SEOPage.info.settings.meta-title-check": "Meta Titre",
3066
+ "SEOPage.info.settings.meta-description-check": "Meta Description",
3067
+ "SEOPage.info.settings.meta-robots-check": "Meta Robots",
3068
+ "SEOPage.info.settings.open-graph-check": "Open Graph",
3069
+ "SEOPage.info.settings.word-count-check": "Compteur de mots",
3070
+ "SEOPage.info.settings.canonical-url-check": "URL Canonique",
3071
+ "SEOPage.info.settings.keyword-density-check": "Densité de mots-clés",
3072
+ "SEOPage.info.settings.structured-data-check": "Données Structurées",
3073
+ "SEOPage.info.settings.alternative-text-check": "Alt text",
3074
+ "SEOPage.info.settings.last-updated-at-check": "Date de dernière mise à jour",
3075
+ "SEOPage.info.settings.cancel.button": "Annuler",
3076
+ "SEOPage.info.settings.save.button": "Enregistrer",
3077
+ "SEOPage.info.settings.information": "Désactiver des verifications SEO pour ce content-type.",
3078
+ "SEOPage.tab.collection-type-title": "Types de collections",
3079
+ "SEOPage.tab.single-type-title": "Types simples",
3080
+ "SEOPage.tab.plugin-title": "Plugins",
3081
+ "SEOPage.tab-panel.column-name": "Nom",
3082
+ "SEOSocialPreview.website-url": "url-of-your-website.io",
3083
+ "SEOSocialPreview.keyword-check": "Vous utilisez {count} mot(s)-clé(s) dans votre {label} ({matches})",
3084
+ "SEOSocialPreview.meta-length-check": "Votre {label} contient {length} caractères (max {max})",
3085
+ "SEOModal.summary-title.meta-title": "Meta Titre",
3086
+ "SEOModal.summary-title.meta-description": "Meta Description",
3087
+ "SEOModal.summary-title.word-counter": "Word Counter",
3088
+ "SEOModal.summary-title.word-densisty": "Densité des mots-clés",
3089
+ "SEOModal.summary-title.open-graph": "Balise Open Graph",
3090
+ "SEOModal.summary-title.canonical-url": "URL canonique",
3091
+ "SEOModal.summary-title.structured-data": "Données Structurées JSON",
3092
+ "SEOModal.summary-title.meta-robots": "Meta Robots",
3093
+ "SEOModal.summary-title.alt-attributes": "Texte d'images",
3094
+ "SEOModal.summary-title.last-updated-at": "Dernière mise à jour",
3095
+ "SEOChecks.keywordsDensityCheck.default": "Chaque mot-clé est utilisé plus de 2 fois.",
3096
+ "SEOChecks.keywordsDensityCheck.no-keywords": "Aucun mot-clé n'a été trouvé dans votre composant SEO.",
3097
+ "SEOChecks.keywordsDensityCheck.one-not-used": "Un mot-clé n'est pas utilisé dans votre contenu.",
3098
+ "SEOChecks.keywordsDensityCheck.one-used-once": "Un mot-clé n'est utilisé qu'une seule fois dans votre contenu.",
3099
+ "SEOChecks.keywordsDensityCheck.label": "Définissez les mots clés pour lesquels vous souhaitez vous ranker dans votre composant SEO. Cela analysera la densité de vos mots-clés dans vos éditeurs de texte enrichi.",
3100
+ "SEOChecks.alternativeTextCheck.default": "Toutes vos images contiennent un attribut alt ! Félicitations!",
3101
+ "SEOChecks.alternativeTextCheck.intersection-zero": "Le nom et le texte alternatif de vos images sont identiques. Strapi génère automatiquement l'attribut alt à partir du nom par défaut si le champ était vide lors du téléchargement du média. Avoir un attribut alt décrivant clairement l'image est une très bonne pratique.",
3102
+ "SEOChecks.alternativeTextCheck.intersection-negative": "images ne dispose(nt) pas d'attribut alt.",
3103
+ "SEOChecks.alternativeTextCheck.richtext-missing-one": "Il manque un attribut alt à au moins une image dans n'importe quel éditeur de texte enrichi de 1er niveau.",
3104
+ "SEOChecks.alternativeTextCheck.label": "Cela vérifiera s'il vous manque du texte alternatif pour vos images (champ média) et dans vos éditeurs de texte enrichi de 1er niveau.",
3105
+ "SEOChecks.alternativeTextCheck.missing-text": "alt manquant dans le champ de texte enrichi suivant :",
3106
+ "SEOChecks.alternativeTextCheck.missing-image": "images sont manquants au premier niveau ou bien dans un composant.",
3107
+ "SEOChecks.alternativeTextCheck.tip-label": "Astuce :",
3108
+ "SEOChecks.alternativeTextCheck.tip-before": "Implémentez une règle dans votre code front-end qui utilise le nom de vos images si le champ",
3109
+ "SEOChecks.alternativeTextCheck.tip-after": "est vide afin de réduire davantage le risque d'un attribut alt vide sur votre site web.",
3110
+ "SEOChecks.canonicalUrlCheck.default": "Aucune URL canonique n'a été trouvée.",
3111
+ "SEOChecks.canonicalUrlCheck.found": "Une URL canonique a été trouvée !",
3112
+ "SEOChecks.canonicalUrlCheck.label": "Cela vérifiera si vous avez une URL canonique.",
3113
+ "SEOChecks.canonicalUrlCheck.url": "URL canonique:",
3114
+ "SEOChecks.lastUpdatedAtCheck.default": "Ce contenu a été modifié il y a plus d'un an ! Les moteurs de recherche aiment le contenu récent.",
3115
+ "SEOChecks.lastUpdatedAtCheck.save-content": "Vous devez d'abord enregistrer cette entité.",
3116
+ "SEOChecks.lastUpdatedAtCheck.success": "Super! Ce contenu a été modifié pour la dernière fois il y a moins d'un an !",
3117
+ "SEOChecks.lastUpdatedAtCheck.label": "Les moteurs de recherche aiment le contenu récent. Cela vérifiera si votre contenu a été modifié pour la dernière fois il y a moins d'un an.",
3118
+ "SEOChecks.lastUpdatedAtCheck.last": "Dernière mise à jour :",
3119
+ "SEOChecks.metaDescriptionCheck.default": "Une meta description a été trouvée !",
3120
+ "SEOChecks.metaDescriptionCheck.not-found": "Aucune meta description n'a été trouvée.",
3121
+ "SEOChecks.metaRobotsCheck.default": "Des balises meta robot ont été trouvées !",
3122
+ "SEOChecks.metaRobotsCheck.not-found": "Aucune balise meta robot n'a été trouvée.",
3123
+ "SEOChecks.metaRobotsCheck.label": "La balise meta du robot informe les moteurs de recherche des pages de votre site qui doivent être indexées et plus encore.",
3124
+ "SEOChecks.metaRobotsCheck.activated": "{tag} est activé : {message}",
3125
+ "SEOChecks.metaRobotsCheck.disabled": "{tag} est désactivé : {message}",
3126
+ "SEOChecks.metaRobotsCheck.notice-label": "Remarque :",
3127
+ "SEOChecks.metaRobotsCheck.notice-before": "Pour ne pas indexer votre entrée et ne pas suivre les liens, votre champ MetaRobots doit contenir :",
3128
+ "SEOChecks.metaRobotsCheck.notice-value": "noindex, nofollow",
3129
+ "SEOChecks.metaRobotsCheck.notice-after": '. Le reste doit être géré par votre logique front-end. "Si le champ contient noindex, vous devez créer la balise meta correspondante, etc."',
3130
+ "SEOChecks.openGraphCheck.not-found": "Aucune balise Open Graph n'a été trouvée.",
3131
+ "SEOChecks.openGraphCheck.not-configured": "Les balises OpenGraph nécessaires ne sont pas configurés.",
3132
+ "SEOChecks.openGraphCheck.configured": "Les balises Open Graph sont configurées.",
3133
+ "SEOChecks.openGraphCheck.label": "Les balises Open Graph vous permettent de gérer le titre, la description et l'image de vos publications.",
3134
+ "SEOChecks.metaTitleCheck.default": "Un Meta Title a été trouvé !",
3135
+ "SEOChecks.metaTitleCheck.not-found": "Aucune meta title n'a été trouvée.",
3136
+ "SEOChecks.structuredDataCheck.default": "Un JSON de données structurées a été trouvé ! Cependant, nous pouvons valider l'exactitude de son contenu.",
3137
+ "SEOChecks.structuredDataCheck.not-found": "Aucun JSON de données structurées n'a été trouvé.",
3138
+ "SEOChecks.structuredDataCheck.label": "Les données structurées sont un format standardisé pour fournir des informations sur une page et classer le contenu de la page.",
3139
+ "SEOChecks.wordCountCheck.default": "Vous avez plus de 300 mots dans vos editeurs de texte enrichi. Le nombre de mots n'est pas un facteur de classement direct. Mais votre contenu doit être de la plus haute qualité possible, avec des informations pertinentes et uniques. Pour répondre à ces conditions, votre contenu nécessite un minimum de paragraphes, et donc de mots",
3140
+ "SEOChecks.wordCountCheck.not-found": "Aucun contenu Richtext n'a été trouvé.",
3141
+ "SEOChecks.wordCountCheck.300": "Votre contenu Richtext de 1er niveau contient moins de 300 mots.",
3142
+ "SEOChecks.wordCountCheck.label": "Votre contenu doit être de la plus haute qualité possible, avec des informations pertinentes et uniques. Votre contenu nécessite un minimum de paragraphes, et donc de mots.",
3143
+ "SEOChecks.wordCountCheck.words": "Mots:",
3144
+ "Modal.seo-component-empty": "Votre composant SEO est vide...",
3145
+ "Modal.seo-component-empty-open-graph": "Les métadonnées Open Graph sont vides...",
3146
+ "Tabs.title-settings": "Paramètres de titres",
3147
+ "Tabs.social-network": "Réseaux sociaux",
3148
+ "Tabs.seo-analyse": "Analyse SEO",
3149
+ "Title-settings.metaTitle-too-long": "La balise meta title est trop long",
3150
+ "Title-settings.metaTitle-tooltip": "La balise de titre est l'une des premières choses que les utilisateurs remarquent dans le SERP. C'est le titre de votre page qui offre un aperçu de ce dont parle votre contenu.",
3151
+ "Title-settings.metaDescription-too-long": "La balise meta description est trop longue",
3152
+ "Title-settings.metaDescription-too-short": "La balise meta description est trop courte",
3153
+ "Title-settings.metaDescription-tooltip": "La méta description est d'une importance égale à la balise de titre. Si la balise de titre est le titre qui apparaît en haut d'un résultat de recherche, la méta description est l'extrait qui s'affiche en dessous.",
3154
+ "Title-settings.metaTitle-hint": "/60 caractères (limite maximale recommandée)",
3155
+ "Title-settings.metaDescription-hint": "/160 caractères (limite maximale recommandée)",
3156
+ "Social-network.metaTitle-hint": "/60 caractères (limite maximale recommandée)",
3157
+ "Social-network.metaDescription-hint": "/65 caractères (limite maximale recommandée)",
3158
+ "Social-network.accordion.description": "Voyez à quoi ressemblera votre contenu posté sur",
3159
+ "Social-network.no-tags": "Vous n'avez pas encore de balises Open Graph.",
3160
+ "Social-network.platform.facebook": "Facebook",
3161
+ "Social-network.platform.twitter": "X (Twitter)",
3162
+ "Social-network.platform.linkedin": "LinkedIn",
3163
+ "Modal.cancel": "Annulez",
3164
+ "Button.see-details": "PLUS DE DETAILS",
3165
+ "Button.open-graph-preview": "Aperçu Open Graph",
3166
+ "Button.browser-preview": "Aperçu Navigateurs",
3167
+ "Button.seo-analyze": "Analyse SEO",
3168
+ Good: Good$2,
3169
+ Improvements: Improvements$2,
3170
+ Bad: Bad$2,
3171
+ "Social-preview.alert": "Complétez votre composant afin d'obtenir un aperçu",
3172
+ Information: Information$2,
3173
+ "notification.success.settings": "Paramètres enregistrés pour le content-type {collectionName}.",
3174
+ "notification.error.settings": "Impossible d'enregistrer les paramètres SEO."
3175
+ };
3176
+ const Good$1 = "Bom";
3177
+ const Improvements$1 = "Pode melhorar";
3178
+ const Bad$1 = "Ruim";
3179
+ const Information$1 = "Informações";
3180
+ const ptBR = {
3181
+ "Plugin.name": "Plugin SEO",
3182
+ "SEOPage.header.button.edit-component": "Editar componente SEO",
3183
+ "SEOPage.header.button.back": "Voltar",
3184
+ "SEOPage.header.title": "SEO",
3185
+ "SEOPage.header.subtitle": "Otimize o seu conteúdo para ser amigável aos mecanismos de busca.",
3186
+ "SEOPage.empty-component-layout.github-error": "Este repositório GitHub não contém componente shared.seo ou shared.open-graph",
3187
+ "SEOPage.empty-component-layout.regexp-error": "Favor fornecer um URL válido do repositório GitHub",
3188
+ "SEOPage.empty-component-layout.no-component": "Você não tem nenhum componente SEO ainda",
3189
+ "SEOPage.empty-component-layout.input.tooltip": "O repositório strapi/components no GitHub contém o componente SEO padrão",
3190
+ "SEOPage.info.information": "Certifique-se de utilizar o componente shared.seo criado, nomeá-lo como 'seo' e adicioná-lo na raiz do seu Content-Type .",
3191
+ "SEOPage.info.no-collection-types": "Você não tem nenhum tipo de coleção ainda...",
3192
+ "SEOPage.info.no-single-types": "Você não tem nenhum tipo singular ainda...",
3193
+ "SEOPage.info.no-plugin-content-type": "Você ainda não tem nenhum tipo de conteúdo de plugin...",
3194
+ "SEOPage.info.create-collection-type": "Criar seu primeiro tipo de coleção",
3195
+ "SEOPage.info.create-single-type": "Criar seu primeiro tipo singular",
3196
+ "SEOPage.info.added": "Adicionado",
3197
+ "SEOPage.info.add": "Adicionar componente",
3198
+ "SEOPage.info.settings": "Configurações",
3199
+ "SEOPage.info.settings.meta-title-check": "Meta Título",
3200
+ "SEOPage.info.settings.meta-description-check": "Meta Descrição",
3201
+ "SEOPage.info.settings.meta-robots-check": "Meta Robots",
3202
+ "SEOPage.info.settings.open-graph-check": "Open Graph",
3203
+ "SEOPage.info.settings.word-count-check": "Contagem de Palavras",
3204
+ "SEOPage.info.settings.canonical-url-check": "URL Canônico",
3205
+ "SEOPage.info.settings.keyword-density-check": "Densidade de Palavras-chave",
3206
+ "SEOPage.info.settings.structured-data-check": "Dados Estruturados",
3207
+ "SEOPage.info.settings.alternative-text-check": "Texto Alternativo (Alt)",
3208
+ "SEOPage.info.settings.last-updated-at-check": "Última Atualização",
3209
+ "SEOPage.info.settings.cancel.button": "Cancelar",
3210
+ "SEOPage.info.settings.save.button": "Salvar",
3211
+ "SEOPage.info.settings.information": "Desativar verificações de SEO para este tipo de conteúdo específico.",
3212
+ "SEOPage.tab.collection-type-title": "Tipo de coleção",
3213
+ "SEOPage.tab.single-type-title": "Tipo simples",
3214
+ "SEOPage.tab.plugin-title": "Plugins",
3215
+ "SEOPage.tab-panel.column-name": "Nome",
3216
+ "SEOSocialPreview.website-url": "url-do-seu-site.com.br",
3217
+ "SEOSocialPreview.keyword-check": "Você usa {count} palavra(s)-chave em seu {label} ({matches})",
3218
+ "SEOSocialPreview.meta-length-check": "Seu {label} tem {length} caracteres (máx. {max})",
3219
+ "SEOModal.summary-title.meta-title": "Meta título",
3220
+ "SEOModal.summary-title.meta-description": "Meta descrição",
3221
+ "SEOModal.summary-title.word-counter": "Contador de palavras",
3222
+ "SEOModal.summary-title.word-densisty": "Densidade de palavras-chave",
3223
+ "SEOModal.summary-title.open-graph": "Tag Open Graph",
3224
+ "SEOModal.summary-title.canonical-url": "URL canónico",
3225
+ "SEOModal.summary-title.structured-data": "Dados estruturados JSON",
3226
+ "SEOModal.summary-title.meta-robots": "Meta Robots",
3227
+ "SEOModal.summary-title.alt-attributes": "Atributo alt",
3228
+ "SEOModal.summary-title.last-updated-at": "Última atualização de conteúdo",
3229
+ "SEOChecks.keywordsDensityCheck.default": "Cada palavra-chave é usada mais de 2 vezes.",
3230
+ "SEOChecks.keywordsDensityCheck.no-keywords": "Não foram encontradas palavras-chave no seu componente SEO.",
3231
+ "SEOChecks.keywordsDensityCheck.one-not-used": "Uma palavra-chave não está sendo utilizada no seu conteúdo.",
3232
+ "SEOChecks.keywordsDensityCheck.one-used-once": "Uma palavra-chave só é usada uma vez no seu conteúdo.",
3233
+ "SEOChecks.keywordsDensityCheck.label": "Defina palavras-chave para as quais quer ser classificado no seu componente SEO. Isto irá analisar a densidade das suas palavras-chave nos seus editores (campos richtext) de 1º nível.",
3234
+ "SEOChecks.alternativeTextCheck.default": "Todas as suas imagens contêm um atributo alt! Parabéns!",
3235
+ "SEOChecks.alternativeTextCheck.intersection-zero": "O nome e o texto alternativo das suas imagens são todos iguais. Strapi gera automaticamente o atributo alt a partir do nome por padrão se o campo estiver vazio durante o carregamento do arquivo. Ter o atributo alt nas imagens descrito claramente é uma prática muito boa.",
3236
+ "SEOChecks.alternativeTextCheck.intersection-negative": "Um ou mais campos de mídia está/estão sem o atributo alt.",
3237
+ "SEOChecks.alternativeTextCheck.richtext-missing-one": "Pelo menos uma imagem em algum editor (campo richtext) de 1º nível está com o atributo alt faltando.",
3238
+ "SEOChecks.alternativeTextCheck.label": "Isto verificará se tem algum texto alternativo em falta para as suas imagens (campo de media) e nos seus editores (campo richtext) de 1º nível.",
3239
+ "SEOChecks.alternativeTextCheck.missing-text": "alteração em falta no seguinte campo richtext:",
3240
+ "SEOChecks.alternativeTextCheck.missing-image": "faltando nos campos de imagem de primeiro nível ou dentro de um componente.",
3241
+ "SEOChecks.alternativeTextCheck.tip-label": "Dica:",
3242
+ "SEOChecks.alternativeTextCheck.tip-before": "Implemente uma regra no seu código front-end que utilize o nome das suas imagens caso o campo",
3243
+ "SEOChecks.alternativeTextCheck.tip-after": "esteja vazio para reduzir ainda mais o risco de um atributo alt vazio no seu site.",
3244
+ "SEOChecks.canonicalUrlCheck.default": "Não foi encontrado nenhum URL canónico.",
3245
+ "SEOChecks.canonicalUrlCheck.found": "Foi encontrado um URL canónico!",
3246
+ "SEOChecks.canonicalUrlCheck.label": "Isto irá verificar se tem um URL canónico.",
3247
+ "SEOChecks.canonicalUrlCheck.url": "URL canónico:",
3248
+ "SEOChecks.lastUpdatedAtCheck.default": "Este conteúdo foi modificado há mais de um ano! Os motores de busca adoram conteúdo atualizados.",
3249
+ "SEOChecks.lastUpdatedAtCheck.save-content": "Deve salvar esta entrada primeiro.",
3250
+ "SEOChecks.lastUpdatedAtCheck.success": "Fantástico! Este conteúdo foi modificado pela última vez há menos de um ano!",
3251
+ "SEOChecks.lastUpdatedAtCheck.label": "Os motores de busca adoram conteúdo atualizado. Isto irá verificar se a sua entrada foi modificada pela última vez há menos de um ano.",
3252
+ "SEOChecks.lastUpdatedAtCheck.last": "Última atualização em:",
3253
+ "SEOChecks.metaDescriptionCheck.default": "Foi encontrada uma Meta Descrição!",
3254
+ "SEOChecks.metaDescriptionCheck.not-found": "Não foi encontrada nenhuma Meta Descrição.",
3255
+ "SEOChecks.metaRobotsCheck.default": "Foram encontrado meta tags Robot!",
3256
+ "SEOChecks.metaRobotsCheck.not-found": "Nenhuma meta tag Robot foi encontrada.",
3257
+ "SEOChecks.metaRobotsCheck.label": "A meta tag Robot informa os motores de busca que páginas do seu site devem ser indexadas e muito mais.",
3258
+ "SEOChecks.metaRobotsCheck.activated": "{tag} está ativado: {message}",
3259
+ "SEOChecks.metaRobotsCheck.disabled": "{tag} está desativado: {message}",
3260
+ "SEOChecks.metaRobotsCheck.notice-label": "Aviso:",
3261
+ "SEOChecks.metaRobotsCheck.notice-before": "Para não indexar sua entrada e não seguir links, seu campo MetaRobots deve conter o seguinte:",
3262
+ "SEOChecks.metaRobotsCheck.notice-value": "noindex, nofollow",
3263
+ "SEOChecks.metaRobotsCheck.notice-after": '. O restante deve ser tratado pela lógica do seu código front-end. "Se o campo contiver noindex, você precisará criar a meta tag correspondente, etc."',
3264
+ "SEOChecks.openGraphCheck.not-found": "Não foram encontradas tags Open Graph.",
3265
+ "SEOChecks.openGraphCheck.not-configured": "As tags mínimas obrigatórias do OpenGraph não estão configuradas.",
3266
+ "SEOChecks.openGraphCheck.configured": "Tags Open Graph estão configuradas",
3267
+ "SEOChecks.openGraphCheck.label": "Tags Open Graph permite você manipular o título, descrição e imagens dos seus posts",
3268
+ "SEOChecks.metaTitleCheck.default": "Uma Meta Título foi encontrada!",
3269
+ "SEOChecks.metaTitleCheck.not-found": "Nenhuma Meta Título foi encontrada.",
3270
+ "SEOChecks.structuredDataCheck.default": "Um Dados estruturados JSON foi encontrado! Entretanto, não podemos validar a exatidão de seu conteúdo.",
3271
+ "SEOChecks.structuredDataCheck.not-found": "Nenhum Dados estruturados JSON foi encontrada.",
3272
+ "SEOChecks.structuredDataCheck.label": "Dados estruturados JSON é um formato padronizado para fornecer informações sobre uma página e classificar o conteúdo da página.",
3273
+ "SEOChecks.wordCountCheck.default": "Você tem mais de 300 palavras. A contagem de palavras não é um fator de classificação direta. Mas seu conteúdo deve ser da mais alta qualidade possível, com informações relevantes e únicas. Para atender a estas condições, seu conteúdo requer um mínimo de parágrafos e, portanto, de palavras.",
3274
+ "SEOChecks.wordCountCheck.not-found": "nenhum conteúdo rico(campo Richtext) foi encontrado.",
3275
+ "SEOChecks.wordCountCheck.300": "Seu conteúdo rico (campo Richtext) de 1º nível tem menos de 300 palavras.",
3276
+ "SEOChecks.wordCountCheck.label": "Seu conteúdo deve ser da mais alta qualidade possível, com informações relevantes e únicas. Sua entrada requer um mínimo de parágrafos e, portanto, de palavras.",
3277
+ "SEOChecks.wordCountCheck.words": "Palavras:",
3278
+ "Modal.seo-component-empty": "Seu componente SEO está vazio...",
3279
+ "Modal.seo-component-empty-open-graph": "Os metadados do Open Graph estão vazios...",
3280
+ "Tabs.title-settings": "Configurações de Título",
3281
+ "Tabs.social-network": "Redes Sociais",
3282
+ "Tabs.seo-analyse": "Analizador de SEO",
3283
+ "Title-settings.metaTitle-too-long": "Meta Título é muito grande.",
3284
+ "Title-settings.metaTitle-tooltip": "A tag de título é uma das primeiras coisas que os usuários notam nos SERPs. É o título de sua página que oferece uma prévia do que se trata seu conteúdo.",
3285
+ "Title-settings.metaDescription-too-long": "Meta Descrição é muito longa.",
3286
+ "Title-settings.metaDescription-too-short": "Meta Descrição é muito curta",
3287
+ "Title-settings.metaDescription-tooltip": "A Meta Descrição é tão importante quanto o título. O título é a informação que aparece no topo de um resultado de busca e a Meta Description é a informação complementar que aparece logo abaixo.",
3288
+ "Title-settings.metaTitle-hint": "/60 caracteres (limite máximo recomendado)",
3289
+ "Title-settings.metaDescription-hint": "/160 caracteres (limite máximo recomendado)",
3290
+ "Social-network.metaTitle-hint": "/60 caracteres (limite máximo recomendado)",
3291
+ "Social-network.metaDescription-hint": "/65 caracteres (limite máximo recomendado)",
3292
+ "Social-network.accordion.description": "Veja como será seu conteúdo publicado em",
3293
+ "Social-network.no-tags": "Você ainda não tem nenhuma tag Open Graph.",
3294
+ "Social-network.platform.facebook": "Facebook",
3295
+ "Social-network.platform.twitter": "X (Twitter)",
3296
+ "Social-network.platform.linkedin": "LinkedIn",
3297
+ "Modal.cancel": "Cancelar",
3298
+ "Button.see-details": "VER DETALHES",
3299
+ "Button.open-graph-preview": "Pré-visualização Open Graph",
3300
+ "Button.browser-preview": "Visualização no Navegador",
3301
+ "Button.seo-analyze": "Analizador SEO",
3302
+ Good: Good$1,
3303
+ Improvements: Improvements$1,
3304
+ Bad: Bad$1,
3305
+ "Social-preview.alert": "Complete as informações do componente de rede social para ver a prévia",
3306
+ Information: Information$1,
3307
+ "notification.success.settings": "Configurações salvas para o tipo de conteúdo {collectionName}.",
3308
+ "notification.error.settings": "Falha ao salvar as configurações de SEO."
3309
+ };
3310
+ const Good = "İyi";
3311
+ const Improvements = "Geliştirilebilir";
3312
+ const Bad = "Kötü";
3313
+ const Information = "Bilgi";
3314
+ const tr = {
3315
+ "Plugin.name": "SEO Eklentisi",
3316
+ "SEOPage.header.button.edit-component": "SEO bileşenini düzenle",
3317
+ "SEOPage.header.button.back": "Geri",
3318
+ "SEOPage.header.title": "SEO",
3319
+ "SEOPage.header.subtitle": "İçeriğinizi arama motorları (SEO) için optimize edin",
3320
+ "SEOPage.empty-component-layout.github-error": "Bu GitHub deposu shared.seo veya shared.open-graph bileşenini içermiyor",
3321
+ "SEOPage.empty-component-layout.regexp-error": "Lütfen geçerli bir GitHub depo URL'si girin",
3322
+ "SEOPage.empty-component-layout.no-component": "Henüz bir SEO bileşeniniz yok",
3323
+ "SEOPage.empty-component-layout.input.tooltip": "strapi/components GitHub deposu varsayılan SEO bileşenini içerir",
3324
+ "SEOPage.info.information": "İçe aktarılan shared.seo bileşenini kullandığınızdan, adını 'seo' olarak belirlediğinizden ve İçerik Türünüzün kök dizinine eklediğinizden emin olun.",
3325
+ "SEOPage.info.no-collection-types": "Henüz hiç koleksiyon türünüz yok...",
3326
+ "SEOPage.info.no-single-types": "Henüz hiç tekil türünüz yok...",
3327
+ "SEOPage.info.no-plugin-content-type": "Henüz hiç eklenti içerik türünüz yok...",
3328
+ "SEOPage.info.create-collection-type": "İlk koleksiyon türünüzü oluşturun",
3329
+ "SEOPage.info.create-single-type": "İlk tekil türünüzü oluşturun",
3330
+ "SEOPage.info.added": "Eklendi",
3331
+ "SEOPage.info.add": "Bileşen ekle",
3332
+ "SEOPage.info.settings": "Ayarlar",
3333
+ "SEOPage.info.settings.meta-title-check": "Meta Başlığı",
3334
+ "SEOPage.info.settings.meta-description-check": "Meta Açıklaması",
3335
+ "SEOPage.info.settings.meta-robots-check": "Meta Robots",
3336
+ "SEOPage.info.settings.open-graph-check": "Open Graph",
3337
+ "SEOPage.info.settings.word-count-check": "Kelime Sayısı",
3338
+ "SEOPage.info.settings.canonical-url-check": "Kanonik URL",
3339
+ "SEOPage.info.settings.keyword-density-check": "Anahtar Kelime Yoğunluğu",
3340
+ "SEOPage.info.settings.structured-data-check": "Yapılandırılmış Veri",
3341
+ "SEOPage.info.settings.alternative-text-check": "Alternatif Metin (Alt)",
3342
+ "SEOPage.info.settings.last-updated-at-check": "Son Güncelleme Tarihi",
3343
+ "SEOPage.info.settings.cancel.button": "İptal",
3344
+ "SEOPage.info.settings.save.button": "Kaydet",
3345
+ "SEOPage.info.settings.information": "Bu içerik türü için belirli SEO kontrollerini devre dışı bırakın.",
3346
+ "SEOPage.tab.collection-type-title": "Koleksiyon Türleri",
3347
+ "SEOPage.tab.single-type-title": "Tekil Türler",
3348
+ "SEOPage.tab.plugin-title": "Eklentiler",
3349
+ "SEOPage.tab-panel.column-name": "Ad",
3350
+ "SEOSocialPreview.website-url": "web-sitenizin-adresi.org",
3351
+ "SEOSocialPreview.keyword-check": "{label} içinde {count} anahtar kelime kullanıyorsunuz ({matches})",
3352
+ "SEOSocialPreview.meta-length-check": "{label} uzunluğunuz {length} karakter (maks. {max})",
3353
+ "SEOModal.summary-title.meta-title": "Meta Başlığı",
3354
+ "SEOModal.summary-title.meta-description": "Meta Açıklaması",
3355
+ "SEOModal.summary-title.word-counter": "Kelime Sayacı",
3356
+ "SEOModal.summary-title.word-densisty": "Anahtar Kelime Yoğunluğu",
3357
+ "SEOModal.summary-title.open-graph": "Open Graph Etiketleri",
3358
+ "SEOModal.summary-title.canonical-url": "Kanonik URL",
3359
+ "SEOModal.summary-title.structured-data": "JSON Yapılandırılmış Veri",
3360
+ "SEOModal.summary-title.meta-robots": "Meta Robots",
3361
+ "SEOModal.summary-title.alt-attributes": "Alternatif Metinler",
3362
+ "SEOModal.summary-title.last-updated-at": "Son güncelleme",
3363
+ "SEOChecks.keywordsDensityCheck.default": "Her anahtar kelime 2 defadan fazla kullanılmış.",
3364
+ "SEOChecks.keywordsDensityCheck.no-keywords": "SEO bileşeninizde anahtar kelime bulunamadı.",
3365
+ "SEOChecks.keywordsDensityCheck.one-not-used": "Bir anahtar kelime içeriğinizde hiç kullanılmıyor.",
3366
+ "SEOChecks.keywordsDensityCheck.one-used-once": "Bir anahtar kelime içeriğinizde yalnızca bir kez kullanılmış.",
3367
+ "SEOChecks.keywordsDensityCheck.label": "SEO bileşeninizde sıralamaya girmek istediğiniz anahtar kelimeleri tanımlayın. Bu, 1. seviye zengin metin düzenleyicilerinizdeki anahtar kelime yoğunluğunu analiz edecektir.",
3368
+ "SEOChecks.alternativeTextCheck.default": "Tüm görselleriniz alt özniteliği içeriyor! Tebrikler!",
3369
+ "SEOChecks.alternativeTextCheck.intersection-zero": "Görsellerinizin dosya adı ve alternatif metni aynı. Medya yükleme sırasında alan boş bırakılırsa Strapi varsayılan olarak dosya adından alt özniteliği oluşturur. Görselleri açıkça tanımlayan alt metinler kullanmak çok iyi bir uygulamadır.",
3370
+ "SEOChecks.alternativeTextCheck.intersection-negative": "medya alanındaki görsel(ler)de alt özniteliği eksik.",
3371
+ "SEOChecks.alternativeTextCheck.richtext-missing-one": "seviye zengin metin düzenleyicilerindeki en az bir görselde alt özniteliği eksik.",
3372
+ "SEOChecks.alternativeTextCheck.label": "Görsellerinizde (medya alanı) ve 1. seviye zengin metin düzenleyicilerinizde eksik alternatif metin olup olmadığını kontrol eder.",
3373
+ "SEOChecks.alternativeTextCheck.missing-text": "aşağıdaki zengin metin alanında alt özniteliği eksik:",
3374
+ "SEOChecks.alternativeTextCheck.missing-image": "birinci seviye görsel alanlarında veya bir bileşen içinde eksik.",
3375
+ "SEOChecks.alternativeTextCheck.tip-label": "İpucu:",
3376
+ "SEOChecks.alternativeTextCheck.tip-before": "Web sitenizde boş bir alt özniteliği riskini azaltmak için",
3377
+ "SEOChecks.alternativeTextCheck.tip-after": "alanı boşsa görsellerinizin adını kullanan bir kuralı ön yüz kodunuzda uygulayın.",
3378
+ "SEOChecks.canonicalUrlCheck.default": "Kanonik URL bulunamadı.",
3379
+ "SEOChecks.canonicalUrlCheck.found": "Bir Kanonik URL bulundu!",
3380
+ "SEOChecks.canonicalUrlCheck.label": "Kanonik URL'nizin olup olmadığını kontrol eder.",
3381
+ "SEOChecks.canonicalUrlCheck.url": "Kanonik URL:",
3382
+ "SEOChecks.lastUpdatedAtCheck.default": "Bu içerik bir yıldan daha uzun süre önce değiştirilmiş! Arama motorları güncel içeriği sever.",
3383
+ "SEOChecks.lastUpdatedAtCheck.save-content": "Önce bu kaydı kaydetmelisiniz.",
3384
+ "SEOChecks.lastUpdatedAtCheck.success": "Harika! Bu içerik bir yıldan daha kısa süre önce güncellendi!",
3385
+ "SEOChecks.lastUpdatedAtCheck.label": "Arama motorları güncel içeriği sever. Bu kontrol, kaydınızın son bir yıl içinde güncellenip güncellenmediğini denetler.",
3386
+ "SEOChecks.lastUpdatedAtCheck.last": "Son güncelleme:",
3387
+ "SEOChecks.metaDescriptionCheck.default": "Meta Açıklaması bulundu!",
3388
+ "SEOChecks.metaDescriptionCheck.not-found": "Meta Açıklaması bulunamadı.",
3389
+ "SEOChecks.metaRobotsCheck.default": "Robot meta etiketleri bulundu!",
3390
+ "SEOChecks.metaRobotsCheck.not-found": "Robot meta etiketi bulunamadı.",
3391
+ "SEOChecks.metaRobotsCheck.label": "Robots meta etiketi, arama motorlarına sitenizdeki hangi sayfaların dizine eklenmesi gerektiğini bildirir.",
3392
+ "SEOChecks.metaRobotsCheck.activated": "{tag} etkinleştirildi: {message}",
3393
+ "SEOChecks.metaRobotsCheck.disabled": "{tag} devre dışı bırakıldı: {message}",
3394
+ "SEOChecks.metaRobotsCheck.notice-label": "Bilgi:",
3395
+ "SEOChecks.metaRobotsCheck.notice-before": "Kaydınızın dizine eklenmemesi ve bağlantıların takip edilmemesi için MetaRobots alanınız şunları içermelidir:",
3396
+ "SEOChecks.metaRobotsCheck.notice-value": "noindex, nofollow",
3397
+ "SEOChecks.metaRobotsCheck.notice-after": ". Geri kalanı ön yüz kod mantığınız tarafından işlenmelidir.",
3398
+ "SEOChecks.openGraphCheck.not-found": "Open Graph etiketi bulunamadı.",
3399
+ "SEOChecks.openGraphCheck.not-configured": "Gerekli minimum OpenGraph etiketleri yapılandırılmamış.",
3400
+ "SEOChecks.openGraphCheck.configured": "Open Graph etiketleri yapılandırıldı",
3401
+ "SEOChecks.openGraphCheck.label": "Open Graph etiketleri, gönderilerinizin başlığını, açıklamasını ve görselini yönetmenize olanak tanır.",
3402
+ "SEOChecks.metaTitleCheck.default": "Meta Başlığı bulundu!",
3403
+ "SEOChecks.metaTitleCheck.not-found": "Meta Başlığı bulunamadı.",
3404
+ "SEOChecks.structuredDataCheck.default": "Yapılandırılmış Veri JSON'u bulundu! Ancak içeriğinin doğruluğunu otomatik olarak doğrulayamıyoruz.",
3405
+ "SEOChecks.structuredDataCheck.not-found": "Yapılandırılmış Veri JSON'u bulunamadı.",
3406
+ "SEOChecks.structuredDataCheck.label": "Yapılandırılmış veri, bir sayfa hakkında bilgi sağlamak ve sayfa içeriğini sınıflandırmak için standartlaştırılmış bir biçimdir.",
3407
+ "SEOChecks.wordCountCheck.default": "300'den fazla kelimeniz var. Kelime sayısı doğrudan bir sıralama faktörü değildir, ancak içeriğiniz ilgili ve benzersiz bilgilerle mümkün olduğunca yüksek kalitede olmalıdır.",
3408
+ "SEOChecks.wordCountCheck.not-found": "Zengin metin içeriği bulunamadı.",
3409
+ "SEOChecks.wordCountCheck.300": "seviye zengin metin içerikleriniz 300 kelimeden az.",
3410
+ "SEOChecks.wordCountCheck.label": "İçeriğiniz ilgili ve benzersiz bilgilerle mümkün olduğunca yüksek kalitede olmalıdır. Kaydınız minimum sayıda paragraf ve kelime gerektirir.",
3411
+ "SEOChecks.wordCountCheck.words": "Kelime sayısı:",
3412
+ "Modal.seo-component-empty": "SEO bileşeniniz boş...",
3413
+ "Modal.seo-component-empty-open-graph": "Open Graph meta verileri boş...",
3414
+ "Tabs.title-settings": "Başlık ayarları",
3415
+ "Tabs.social-network": "Sosyal ağlar",
3416
+ "Tabs.seo-analyse": "SEO analizi",
3417
+ "Title-settings.metaTitle-too-long": "Meta Başlığı çok uzun",
3418
+ "Title-settings.metaTitle-tooltip": "Başlık etiketi, kullanıcıların arama sonuçlarında fark ettiği ilk şeylerden biridir ve içeriğinizin ne hakkında olduğunun önizlemesini sunar.",
3419
+ "Title-settings.metaDescription-too-long": "Meta Açıklaması çok uzun",
3420
+ "Title-settings.metaDescription-too-short": "Meta Açıklaması çok kısa",
3421
+ "Title-settings.metaDescription-tooltip": "Meta açıklaması da başlık etiketi kadar önemlidir. Arama sonuçlarında başlığın hemen altında görüntülenen özet metindir.",
3422
+ "Title-settings.metaTitle-hint": "/60 karakter (önerilen maksimum sınır)",
3423
+ "Title-settings.metaDescription-hint": "/160 karakter (önerilen maksimum sınır)",
3424
+ "Social-network.metaTitle-hint": "/60 karakter (önerilen maksimum sınır)",
3425
+ "Social-network.metaDescription-hint": "/65 karakter (önerilen maksimum sınır)",
3426
+ "Social-network.accordion.description": "İçeriğinizin şu platformda paylaşıldığında nasıl görüneceğini inceleyin:",
3427
+ "Social-network.no-tags": "Henüz hiç Open Graph etiketiniz yok.",
3428
+ "Social-network.platform.facebook": "Facebook",
3429
+ "Social-network.platform.twitter": "X (Twitter)",
3430
+ "Social-network.platform.linkedin": "LinkedIn",
3431
+ "Modal.cancel": "İptal",
3432
+ "Button.see-details": "DETAYLARI GÖR",
3433
+ "Button.open-graph-preview": "Open Graph Önizlemesi",
3434
+ "Button.browser-preview": "Tarayıcı Önizlemesi",
3435
+ "Button.seo-analyze": "SEO Analizi",
3436
+ Good,
3437
+ Improvements,
3438
+ Bad,
3439
+ "Social-preview.alert": "Önizlemeyi görmek için sosyal ağ bileşeninizi tamamlayın",
3440
+ Information,
3441
+ "notification.success.settings": "{collectionName} içerik türü için ayarlar kaydedildi.",
3442
+ "notification.error.settings": "SEO ayarları kaydedilemedi."
3443
+ };
3444
+ const name = pluginPkg.strapi.name;
3445
+ const TRAD_MAP = {
3446
+ en,
3447
+ es,
3448
+ ar,
3449
+ tr,
3450
+ fr,
3451
+ "pt-BR": ptBR
3452
+ };
3453
+ const index = {
3454
+ register(app) {
3455
+ app.addMenuLink({
3456
+ to: `${pluginId}`,
3457
+ icon: icons.Search,
3458
+ permissions: pluginPermissions.main,
3459
+ intlLabel: {
3460
+ id: `${pluginId}.Plugin.name`,
3461
+ defaultMessage: "SEO"
3462
+ },
3463
+ Component: () => Promise.resolve().then(() => require("./index-CZ4VBOGS.cjs"))
3464
+ });
3465
+ app.registerPlugin({
3466
+ id: pluginId,
3467
+ initializer: Initializer,
3468
+ isReady: false,
3469
+ name
3470
+ });
3471
+ },
3472
+ bootstrap(app) {
3473
+ app.getPlugin("content-manager").injectComponent("editView", "right-links", {
3474
+ name: "SeoChecker",
3475
+ Component: SeoChecker
3476
+ });
3477
+ },
3478
+ registerTrads: createRegisterTrads({
3479
+ pluginId,
3480
+ defaultLocales: [],
3481
+ keepUnprefixed: false,
3482
+ translations: TRAD_MAP
3483
+ })
3484
+ };
3485
+ exports.Illo = Illo;
3486
+ exports.getTrad = getTrad;
3487
+ exports.index = index;
3488
+ exports.pluginId = pluginId;
3489
+ exports.pluginPermissions = pluginPermissions;
3490
+ exports.useSettingsApi = useSettingsApi;