docs: синхронизировать английское руководство (#668)

Английская версия догнана до структуры и содержания русского руководства. Структурный гейт защищает H2-H4 и маркер актуальной версии от нового рассинхрона.

Issue: #668
User-Visible: no
This commit is contained in:
Sergey Matyunin
2026-09-27 09:18:50 +00:00
committed by claude[bot]
parent cbc39cea7b
commit 85cba3d8b4
4 changed files with 904 additions and 202 deletions
+827 -202
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
import { DOC_SCREENSHOTS } from '../demo/docs/screenshots.mjs';
import { visualFingerprint } from './source-fingerprint.mjs';
import { freshnessSink, screenshotsMode } from './docs-freshness.mjs';
import { guideParityErrors } from './user-guide-parity.mjs';
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const EXTERNAL = process.argv.includes('--external');
@@ -184,6 +185,11 @@ for (const [en, ru, required] of [
}
}
errors.push(...guideParityErrors(
canonicalText(resolve(ROOT, 'docs/USER-GUIDE.md')),
canonicalText(resolve(ROOT, 'docs/USER-GUIDE.ru.md')),
));
const staleTerms = [
[/\bMarkup (?:tab|mode|editor)\b/gi, 'Plan'],
[/(?:вкладка|режим|редактор) «Разметка»/gi, '«План»'],
+35
View File
@@ -0,0 +1,35 @@
const withoutFences = (text) => text.replace(/^```[^\n]*\n[\s\S]*?^```\s*$/gm, '');
export const guideHeadingProfile = (text) => withoutFences(text)
.split(/\r?\n/)
.flatMap((line) => {
const match = line.match(/^(#{2,4})\s+(.+?)\s*#*\s*$/);
if (!match) return [];
const section = match[1].length === 2 ? match[2].match(/^(\d+)\./)?.[1] ?? null : null;
return [{ level: match[1].length, section }];
});
export const guideVersion = (text, language) => {
const pattern = language === 'ru'
? /^Актуально для \*\*(v[^*]+)\*\*\./m
: /^Current for \*\*(v[^*]+)\*\*\./m;
return text.match(pattern)?.[1] ?? null;
};
export const guideParityErrors = (english, russian) => {
const errors = [];
const englishProfile = guideHeadingProfile(english);
const russianProfile = guideHeadingProfile(russian);
if (JSON.stringify(englishProfile) !== JSON.stringify(russianProfile)) {
errors.push('docs/USER-GUIDE.md / docs/USER-GUIDE.ru.md: H2-H4 heading profiles differ');
}
const englishVersion = guideVersion(english, 'en');
const russianVersion = guideVersion(russian, 'ru');
if (!englishVersion || !russianVersion) {
errors.push('docs/USER-GUIDE.md / docs/USER-GUIDE.ru.md: current-version marker is missing');
} else if (englishVersion !== russianVersion) {
errors.push(`docs/USER-GUIDE.md / docs/USER-GUIDE.ru.md: current versions differ (${englishVersion} != ${russianVersion})`);
}
return errors;
};
+36
View File
@@ -0,0 +1,36 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
import {
guideHeadingProfile,
guideParityErrors,
guideVersion,
} from '../scripts/user-guide-parity.mjs';
const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), 'utf8');
test('#668: English and Russian user guides keep the same structure and version', () => {
const english = read('docs/USER-GUIDE.md');
const russian = read('docs/USER-GUIDE.ru.md');
assert.deepEqual(guideParityErrors(english, russian), []);
assert.equal(guideHeadingProfile(english).filter(({ section }) => section).length, 23);
assert.equal(guideVersion(english, 'en'), guideVersion(russian, 'ru'));
});
test('#668: removing a subsection makes the parity guard fail', () => {
const english = '## 1. One\n### Detail\n## 2. Two\n';
const russian = '## 1. Один\n## 2. Два\n';
assert.match(guideParityErrors(english, russian).join('\n'), /heading profiles differ/);
});
test('#668: changing a subsection level makes the parity guard fail', () => {
const english = '## 1. One\n### Detail\n';
const russian = '## 1. Один\n#### Деталь\n';
assert.match(guideParityErrors(english, russian).join('\n'), /heading profiles differ/);
});
test('#668: different guide version markers make the parity guard fail', () => {
const english = 'Current for **v1.2.3**.\n';
const russian = 'Актуально для **v1.2.4**.\n';
assert.match(guideParityErrors(english, russian).join('\n'), /current versions differ/);
});