feat(docs): mirror every docs/ page in all 65 locales (#14106)

* feat(docs): mirror every docs/ page in all 65 locales

Extends the documentation mirrors from the 22-page core set (#13940) to
every Markdown page under docs/: 152 sources x 65 locales = 9,880 mirrors
(6,208 new), language bars rewritten for the full locale list, state
adopted so the blocking drift gate now covers all 152 pages.

run-translation.mjs: an oversized block made only of table rows or list
items (PROVIDER_REFERENCE.md 244-row table, FREE_TIERS.md 71-item list) is
cut at item boundaries and rejoined without a blank line — the single
16-40 KB request outlived the backend socket for verbose scripts. 48
older mirrors whose tables had lost rows were retranslated with --force.

* docs(i18n): refresh mirrors for the sources the base changed since the branch cut

Section-level retranslation of the 29 docs (and README.md) whose source
or mirrors moved on release/v3.8.51 during the run, then state adoption;
the drift gate is green again on the merged tree.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-09-18 13:16:46 -03:00
committed by GitHub
parent c059b77823
commit 8feea123bb
9014 changed files with 2438195 additions and 38940 deletions

61
scripts/i18n/run-translation.mjs Executable file → Normal file
View File

@@ -462,7 +462,7 @@ function splitOversizedSection(section, maxChars) {
const chunks = [];
let current = [];
let size = 0;
for (const lines of blocks) {
for (const lines of blocks.flatMap((b) => splitOversizedRun(b, maxChars))) {
const length = lines.join("\n").length + 1;
if (size > 0 && size + length > maxChars) {
chunks.push(current.join("\n"));
@@ -476,6 +476,63 @@ function splitOversizedSection(section, maxChars) {
return chunks;
}
// A markdown table or a long bullet list has no blank line inside it, so the
// paragraph splitter kept PROVIDER_REFERENCE.md's 244-row table (40 KB) and
// FREE_TIERS.md's 71-item list (16 KB) as one block each, and the request for
// a verbose script outlived the upstream socket ("fetch failed" for Greek and
// Amharic on every attempt). An oversized block made only of table rows or
// list items (plus their indented continuation lines) is cut before an item
// line; the table header rows travel with the first group only.
const ITEM_LINE = /^\s*(\||[-*+]\s|\d+[.)]\s)/;
const CONTINUATION_LINE = /^\s+\S/;
function splitOversizedRun(lines, maxChars) {
if (lines.join("\n").length <= maxChars) return [lines];
const content = lines.filter((l) => l.trim() !== "");
if (!content.every((l) => ITEM_LINE.test(l) || CONTINUATION_LINE.test(l))) return [lines];
if (!ITEM_LINE.test(content[0])) return [lines];
const groups = [];
let group = [];
let size = 0;
for (const line of lines) {
if (group.length && ITEM_LINE.test(line) && size + line.length + 1 > maxChars) {
groups.push(group);
group = [];
size = 0;
}
group.push(line);
size += line.length + 1;
}
if (group.length) groups.push(group);
return groups;
}
// Chunks are rejoined with a blank line (they were cut on headings and
// paragraphs) — except at a seam between two table rows or two list items,
// where a blank line would break one table (or one tight list) into two.
// True when the text ends with a table row or a list item (its indented
// continuation lines included), i.e. a following item line belongs to the
// same run.
function endsInsideItemRun(text) {
const lines = text.trimEnd().split("\n");
let i = lines.length - 1;
while (i > 0 && CONTINUATION_LINE.test(lines[i])) i--;
return ITEM_LINE.test(lines[i] ?? "");
}
export function joinTranslatedChunks(parts) {
let out = "";
for (let i = 0; i < parts.length; i++) {
if (i === 0) {
out = parts[i];
continue;
}
const nextFirst = parts[i].trimStart().split("\n")[0] ?? "";
const seam = endsInsideItemRun(out) && ITEM_LINE.test(nextFirst) ? "\n" : "\n\n";
out = out.trimEnd() + seam + parts[i].trimStart();
}
return out;
}
// ----- Section cache --------------------------------------------------------
// A mirror is retranslated section by section: the state remembers a short
// hash of every `## ` section of the source at the time of translation, and a
@@ -691,7 +748,7 @@ async function translateBody(body, localeEntry, backend) {
// reliably converts characters but not vocabulary habits, so zh-TW output
// otherwise keeps mainland renderings (默認 for 預設, 緩存 for 快取) and
// wrong-homophone conversions (上遊 for 上游, 儀錶板 for 儀表板).
return normalizeLocaleText(translated.join("\n\n"), localeEntry.code);
return normalizeLocaleText(joinTranslatedChunks(translated), localeEntry.code);
}
// Simple promise-based semaphore (avoid runtime deps).