Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 10x 10x 10x 140x 10x 10x 7x 1x 1x 9x 9x 11x 9x 11x 11x 10x 10x 10x 10x 10x 10x 10x 7x 3x 3x 1x 8x 8x 8x | // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
/**
* @module Generators/NewsIndexes/BackfillHreflang
* @description Hreflang alternate-link backfill for article HTML files.
* Extracted from `backfill.ts` to keep source files ≤600 lines.
*/
import path from 'path';
import fs from 'fs';
import { NEWS_DIR, BASE_URL } from '../../constants/config.js';
import { ALL_LANGUAGES } from '../../constants/languages.js';
import { parseArticleFilename, atomicWrite } from '../../utils/file-utils.js';
/**
* Read an article HTML file, returning an empty string when unavailable.
*
* @param filepath - Absolute HTML file path
* @returns File content or empty string
*/
function readArticleHtml(filepath: string): string {
try {
return path.isAbsolute(filepath) ? fs.readFileSync(filepath, 'utf8') : '';
} catch {
return '';
}
}
/**
* Build hreflang `<link rel="alternate">` tags for an article slug.
* Produces one tag per supported language plus an `x-default` pointing at
* the English variant, all using absolute URLs.
*
* @param articleSlug - Slug without language suffix (e.g. `2026-02-24-propositions`)
* @returns Newline-joined `<link>` tags
*/
function buildArticleHreflang(articleSlug: string): string {
const entries = ALL_LANGUAGES.map(
(code) =>
` <link rel="alternate" hreflang="${code}" href="${BASE_URL}/news/${articleSlug}-${code}.html">`
);
entries.push(
` <link rel="alternate" hreflang="x-default" href="${BASE_URL}/news/${articleSlug}-en.html">`
);
return entries.join('\n');
}
/**
* Inject hreflang links into an article that has none.
*
* @param html - Article HTML content
* @param hreflangBlock - Pre-built hreflang link block
* @returns Updated HTML, or original if no change needed
*/
function injectHreflangLinks(html: string, hreflangBlock: string): string {
return html.replace(/(<\/head>)/u, `${hreflangBlock}\n$1`);
}
/**
* Replace existing relative hreflang links with absolute URLs.
*
* @param html - Article HTML content
* @param hreflangBlock - Pre-built hreflang link block with absolute URLs
* @returns Updated HTML, or original if no change needed
*/
function fixRelativeHreflangLinks(html: string, hreflangBlock: string): string {
const stripped = html.replace(
/\s*<link\s+rel="alternate"\s+hreflang="[^"]*"\s+href="[^"]*">\n?/gu,
''
);
return stripped.replace(/(<\/head>)/u, `${hreflangBlock}\n$1`);
}
/**
* Backfill hreflang alternate links for all article HTML files.
*
* Handles three cases:
* 1. Articles with no hreflang links at all → inject the full block before `</head>`
* 2. Articles with relative hreflang URLs → replace with absolute URLs
* 3. Articles already correct → skip
*
* @param filenames - News article filenames
* @returns Number of HTML files updated
*/
export function backfillArticleHreflang(filenames: readonly string[]): number {
let updated = 0;
for (const filename of filenames) {
if (backfillOneArticleHreflang(filename)) updated++;
}
return updated;
}
/**
* Backfill hreflang for a single article file.
*
* @param filename - News article filename
* @returns True when the file was updated
*/
function backfillOneArticleHreflang(filename: string): boolean {
const parsed = parseArticleFilename(filename);
if (!parsed) return false;
const filepath = path.join(NEWS_DIR, filename);
const html = readArticleHtml(filepath);
Iif (!html) return false;
const articleSlug = `${parsed.date}-${parsed.slug}`;
const hreflangBlock = buildArticleHreflang(articleSlug);
const hasHreflang = /<link\s+rel="alternate"\s+hreflang="/u.test(html);
let next: string;
if (!hasHreflang) {
next = injectHreflangLinks(html, hreflangBlock);
} else {
const hasRelative = /<link\s+rel="alternate"\s+hreflang="[^"]*"\s+href="(?!https?:\/\/)/u.test(
html
);
if (!hasRelative) return false;
next = fixRelativeHreflangLinks(html, hreflangBlock);
}
Iif (next === html) return false;
atomicWrite(filepath, next);
return true;
}
|