@ijonis/geo-lint 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +68 -35
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +68 -35
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +70 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +70 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -448,7 +448,7 @@ function createLinkExtractor(siteUrl) {
|
|
|
448
448
|
// src/utils/slug-resolver.ts
|
|
449
449
|
var import_node_fs4 = require("fs");
|
|
450
450
|
var import_node_path3 = require("path");
|
|
451
|
-
function extractSlugFromFile(filePath) {
|
|
451
|
+
function extractSlugFromFile(filePath, defaultLocale = "de") {
|
|
452
452
|
try {
|
|
453
453
|
const content = (0, import_node_fs4.readFileSync)(filePath, "utf-8");
|
|
454
454
|
if (!content.startsWith("---")) return null;
|
|
@@ -462,7 +462,7 @@ function extractSlugFromFile(filePath) {
|
|
|
462
462
|
if (draftMatch) return null;
|
|
463
463
|
return {
|
|
464
464
|
slug: slugMatch[1].trim(),
|
|
465
|
-
locale: localeMatch ? localeMatch[1].trim() :
|
|
465
|
+
locale: localeMatch ? localeMatch[1].trim() : defaultLocale
|
|
466
466
|
};
|
|
467
467
|
} catch {
|
|
468
468
|
return null;
|
|
@@ -491,12 +491,12 @@ function scanRawContentPermalinks(knownSlugs, contentPaths) {
|
|
|
491
491
|
const contentDir = (0, import_node_path3.join)(projectRoot, pathConfig.dir);
|
|
492
492
|
if (!(0, import_node_fs4.existsSync)(contentDir)) continue;
|
|
493
493
|
const urlPrefix = pathConfig.urlPrefix ?? "/";
|
|
494
|
+
const defaultLocale = pathConfig.defaultLocale ?? "de";
|
|
494
495
|
for (const file of findFilesInDir(contentDir, ".mdx")) {
|
|
495
|
-
const meta = extractSlugFromFile(file);
|
|
496
|
+
const meta = extractSlugFromFile(file, defaultLocale);
|
|
496
497
|
if (!meta) continue;
|
|
497
498
|
let permalink;
|
|
498
|
-
|
|
499
|
-
if (meta.locale !== defaultLocale && meta.locale !== "de") {
|
|
499
|
+
if (meta.locale !== defaultLocale) {
|
|
500
500
|
permalink = `/${meta.locale}${urlPrefix}${meta.slug}`.replace(/\/+/g, "/");
|
|
501
501
|
} else {
|
|
502
502
|
permalink = `${urlPrefix}${meta.slug}`.replace(/\/+/g, "/");
|
|
@@ -1221,7 +1221,7 @@ function countSentences(text) {
|
|
|
1221
1221
|
}
|
|
1222
1222
|
|
|
1223
1223
|
// src/utils/readability.ts
|
|
1224
|
-
function estimateSyllables(word) {
|
|
1224
|
+
function estimateSyllables(word, locale = "de") {
|
|
1225
1225
|
const lower = word.toLowerCase();
|
|
1226
1226
|
const vowelPattern = /[aeiouyäöü]+/gi;
|
|
1227
1227
|
const matches = lower.match(vowelPattern);
|
|
@@ -1229,17 +1229,21 @@ function estimateSyllables(word) {
|
|
|
1229
1229
|
return 1;
|
|
1230
1230
|
}
|
|
1231
1231
|
let count = matches.length;
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1232
|
+
const lang = locale.toLowerCase().slice(0, 2);
|
|
1233
|
+
if (lang === "en") {
|
|
1234
|
+
if (lower.endsWith("e") && count > 1) {
|
|
1235
|
+
count -= 0.5;
|
|
1236
|
+
}
|
|
1237
|
+
} else {
|
|
1238
|
+
if (word.length > 12) {
|
|
1239
|
+
count = Math.max(count, Math.ceil(word.length / 4));
|
|
1240
|
+
}
|
|
1237
1241
|
}
|
|
1238
1242
|
return Math.max(1, Math.round(count));
|
|
1239
1243
|
}
|
|
1240
|
-
function countSyllables(text) {
|
|
1244
|
+
function countSyllables(text, locale) {
|
|
1241
1245
|
const words = text.split(/\s+/).filter((word) => word.length > 0).filter((word) => /\w/.test(word));
|
|
1242
|
-
return words.reduce((total, word) => total + estimateSyllables(word), 0);
|
|
1246
|
+
return words.reduce((total, word) => total + estimateSyllables(word, locale), 0);
|
|
1243
1247
|
}
|
|
1244
1248
|
function averageWordLength(text) {
|
|
1245
1249
|
const words = text.split(/\s+/).filter((word) => word.length > 0).filter((word) => /\w/.test(word));
|
|
@@ -1247,11 +1251,35 @@ function averageWordLength(text) {
|
|
|
1247
1251
|
const totalLength = words.reduce((sum, word) => sum + word.length, 0);
|
|
1248
1252
|
return totalLength / words.length;
|
|
1249
1253
|
}
|
|
1250
|
-
|
|
1254
|
+
var FLESCH_COEFFICIENTS = {
|
|
1255
|
+
en: { intercept: 206.835, aslWeight: 1.015, aswWeight: 84.6 },
|
|
1256
|
+
de: { intercept: 180, aslWeight: 1, aswWeight: 58.5 }
|
|
1257
|
+
};
|
|
1258
|
+
var INTERPRETATION_BANDS = {
|
|
1259
|
+
en: [
|
|
1260
|
+
{ min: 90, label: "Very easy to read" },
|
|
1261
|
+
{ min: 80, label: "Easy to read" },
|
|
1262
|
+
{ min: 70, label: "Fairly easy to read" },
|
|
1263
|
+
{ min: 60, label: "Standard" },
|
|
1264
|
+
{ min: 50, label: "Fairly difficult" },
|
|
1265
|
+
{ min: 30, label: "Difficult" },
|
|
1266
|
+
{ min: 0, label: "Very difficult" }
|
|
1267
|
+
],
|
|
1268
|
+
de: [
|
|
1269
|
+
{ min: 70, label: "Very easy to read" },
|
|
1270
|
+
{ min: 60, label: "Easy to read" },
|
|
1271
|
+
{ min: 50, label: "Fairly easy to read" },
|
|
1272
|
+
{ min: 40, label: "Standard" },
|
|
1273
|
+
{ min: 30, label: "Fairly difficult" },
|
|
1274
|
+
{ min: 20, label: "Difficult" },
|
|
1275
|
+
{ min: 0, label: "Very difficult" }
|
|
1276
|
+
]
|
|
1277
|
+
};
|
|
1278
|
+
function calculateReadability(mdxBody, locale = "de") {
|
|
1251
1279
|
const plainText = stripMarkdown(mdxBody);
|
|
1252
1280
|
const wordCount = countWords(mdxBody);
|
|
1253
1281
|
const sentenceCount = countSentences(mdxBody);
|
|
1254
|
-
const syllableCount = countSyllables(plainText);
|
|
1282
|
+
const syllableCount = countSyllables(plainText, locale);
|
|
1255
1283
|
if (wordCount === 0 || sentenceCount === 0) {
|
|
1256
1284
|
return {
|
|
1257
1285
|
score: 0,
|
|
@@ -1264,23 +1292,26 @@ function calculateReadability(mdxBody) {
|
|
|
1264
1292
|
const avgSentenceLength = wordCount / sentenceCount;
|
|
1265
1293
|
const avgSyllablesPerWord = syllableCount / wordCount;
|
|
1266
1294
|
const avgWordLen = averageWordLength(plainText);
|
|
1267
|
-
const
|
|
1295
|
+
const lang = locale.toLowerCase().slice(0, 2);
|
|
1296
|
+
const coefficients = FLESCH_COEFFICIENTS[lang] ?? FLESCH_COEFFICIENTS.de;
|
|
1297
|
+
const score = Math.round(
|
|
1298
|
+
coefficients.intercept - coefficients.aslWeight * avgSentenceLength - coefficients.aswWeight * avgSyllablesPerWord
|
|
1299
|
+
);
|
|
1268
1300
|
const clampedScore = Math.max(0, Math.min(100, score));
|
|
1269
1301
|
return {
|
|
1270
1302
|
score: clampedScore,
|
|
1271
1303
|
avgSentenceLength: Math.round(avgSentenceLength * 10) / 10,
|
|
1272
1304
|
avgSyllablesPerWord: Math.round(avgSyllablesPerWord * 100) / 100,
|
|
1273
1305
|
avgWordLength: Math.round(avgWordLen * 10) / 10,
|
|
1274
|
-
interpretation: getInterpretation(clampedScore)
|
|
1306
|
+
interpretation: getInterpretation(clampedScore, locale)
|
|
1275
1307
|
};
|
|
1276
1308
|
}
|
|
1277
|
-
function getInterpretation(score) {
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
if (score >= 20) return "Difficult";
|
|
1309
|
+
function getInterpretation(score, locale = "de") {
|
|
1310
|
+
const lang = locale.toLowerCase().slice(0, 2);
|
|
1311
|
+
const bands = INTERPRETATION_BANDS[lang] ?? INTERPRETATION_BANDS.de;
|
|
1312
|
+
for (const band of bands) {
|
|
1313
|
+
if (score >= band.min) return band.label;
|
|
1314
|
+
}
|
|
1284
1315
|
return "Very difficult";
|
|
1285
1316
|
}
|
|
1286
1317
|
function isReadable(score, threshold) {
|
|
@@ -1321,7 +1352,8 @@ var lowReadability = {
|
|
|
1321
1352
|
return [];
|
|
1322
1353
|
}
|
|
1323
1354
|
const c = context.thresholds ? resolveThresholds(context.thresholds, item.contentType).content : CONTENT_DEFAULTS;
|
|
1324
|
-
const
|
|
1355
|
+
const locale = item.locale ?? context.defaultLocale ?? "de";
|
|
1356
|
+
const readability = calculateReadability(item.body, locale);
|
|
1325
1357
|
if (!isReadable(readability.score, c.minReadabilityScore)) {
|
|
1326
1358
|
const isExtreme = readability.score < 20;
|
|
1327
1359
|
const severity = isExtreme ? "error" : "warning";
|
|
@@ -13605,9 +13637,9 @@ var DEFAULT_CONFIG2 = {
|
|
|
13605
13637
|
function getLocaleConfig(locale) {
|
|
13606
13638
|
return LOCALE_CONFIGS[locale.toLowerCase()] ?? DEFAULT_CONFIG2;
|
|
13607
13639
|
}
|
|
13608
|
-
function isComplexWord(normalizedWord, originalWord, config, frequencyList) {
|
|
13640
|
+
function isComplexWord(normalizedWord, originalWord, config, frequencyList, locale = "en") {
|
|
13609
13641
|
if (normalizedWord.length <= config.minLength) return false;
|
|
13610
|
-
if (estimateSyllables(normalizedWord) < config.minSyllables) return false;
|
|
13642
|
+
if (estimateSyllables(normalizedWord, locale) < config.minSyllables) return false;
|
|
13611
13643
|
if (frequencyList?.has(normalizedWord)) return false;
|
|
13612
13644
|
if (config.skipCapitalized && /^[A-Z]/.test(originalWord)) return false;
|
|
13613
13645
|
return true;
|
|
@@ -13622,7 +13654,7 @@ function analyzeWordComplexity(body, locale = "en") {
|
|
|
13622
13654
|
let complexCount = 0;
|
|
13623
13655
|
for (const original of rawWords) {
|
|
13624
13656
|
const normalized = original.toLowerCase();
|
|
13625
|
-
if (isComplexWord(normalized, original, config, frequencyList)) {
|
|
13657
|
+
if (isComplexWord(normalized, original, config, frequencyList, locale)) {
|
|
13626
13658
|
complexCount++;
|
|
13627
13659
|
complexCounts.set(normalized, (complexCounts.get(normalized) ?? 0) + 1);
|
|
13628
13660
|
}
|
|
@@ -15372,10 +15404,10 @@ var jargonDensity = {
|
|
|
15372
15404
|
severity: "warning",
|
|
15373
15405
|
category: "content",
|
|
15374
15406
|
fixStrategy: "Replace complex or uncommon words with simpler alternatives",
|
|
15375
|
-
run: (item) => {
|
|
15407
|
+
run: (item, context) => {
|
|
15376
15408
|
const wordCount = countWords(item.body);
|
|
15377
15409
|
if (wordCount < QUALITY_MIN_WORDS) return [];
|
|
15378
|
-
const locale = item.locale ?? "en";
|
|
15410
|
+
const locale = item.locale ?? context.defaultLocale ?? "en";
|
|
15379
15411
|
const analysis = analyzeJargonDensity(item.body, locale);
|
|
15380
15412
|
if (analysis.density >= JARGON_ERROR_THRESHOLD) {
|
|
15381
15413
|
const topWords = analysis.topJargonWords.slice(0, 3).map((w) => `"${w.word}" (${w.count}x)`).join(", ");
|
|
@@ -15516,10 +15548,10 @@ var lowTransitionWords = {
|
|
|
15516
15548
|
severity: "warning",
|
|
15517
15549
|
category: "content",
|
|
15518
15550
|
fixStrategy: "Add transition words (however, therefore, for example, in addition) to connect ideas between sentences",
|
|
15519
|
-
run: (item) => {
|
|
15551
|
+
run: (item, context) => {
|
|
15520
15552
|
const wordCount = countWords(item.body);
|
|
15521
15553
|
if (wordCount < TRANSITION_MIN_WORDS) return [];
|
|
15522
|
-
const locale = item.locale ?? "en";
|
|
15554
|
+
const locale = item.locale ?? context.defaultLocale ?? "en";
|
|
15523
15555
|
const analysis = analyzeTransitionWords(item.body, locale);
|
|
15524
15556
|
if (analysis.totalSentences < 5) return [];
|
|
15525
15557
|
if (analysis.ratio < TRANSITION_ERROR_THRESHOLD) {
|
|
@@ -15550,10 +15582,10 @@ var consecutiveStarts = {
|
|
|
15550
15582
|
severity: "warning",
|
|
15551
15583
|
category: "content",
|
|
15552
15584
|
fixStrategy: "Vary sentence openings \u2014 use transition words, prepositional phrases, or reversed structures",
|
|
15553
|
-
run: (item) => {
|
|
15585
|
+
run: (item, context) => {
|
|
15554
15586
|
const wordCount = countWords(item.body);
|
|
15555
15587
|
if (wordCount < CONSECUTIVE_STARTS_MIN_WORDS) return [];
|
|
15556
|
-
const locale = item.locale ?? "en";
|
|
15588
|
+
const locale = item.locale ?? context.defaultLocale ?? "en";
|
|
15557
15589
|
const analysis = analyzeSentenceBeginnings(item.body, locale);
|
|
15558
15590
|
if (analysis.groups.length === 0) return [];
|
|
15559
15591
|
const worst = analysis.groups.reduce((a, b) => a.count > b.count ? a : b);
|
|
@@ -15801,7 +15833,8 @@ async function lint(options = {}) {
|
|
|
15801
15833
|
validSlugs: buildSlugRegistry(contentItems, config.staticRoutes, config.contentPaths),
|
|
15802
15834
|
validImages: buildImageRegistry(config.imageDirectories),
|
|
15803
15835
|
thresholds: config.thresholds,
|
|
15804
|
-
geoEnabledContentTypes: config.geo.enabledContentTypes ?? ["blog"]
|
|
15836
|
+
geoEnabledContentTypes: config.geo.enabledContentTypes ?? ["blog"],
|
|
15837
|
+
defaultLocale: config.i18n.defaultLocale
|
|
15805
15838
|
};
|
|
15806
15839
|
if (isPretty) {
|
|
15807
15840
|
printProgress(`Found ${context.validSlugs.size} valid URLs, ${context.validImages.size} images`);
|