feat(i18n): 9 new locales — all 24 official EU languages (51 locales) (#13044)

Batch 1 of the locale expansion: Greek, Croatian, Serbian, Lithuanian, Estonian, Latvian, Slovenian, Maltese and Irish across the dashboard catalog, docs mirrors, CLI catalog, README, locale index and the site. 42 → 51 locales.

Also fixes the ICU literal escape the translation backend dropped around angle placeholders, four translations that invented or renamed a placeholder, the language bars that linked to mirrors that do not exist, and the migration count drift (171 → 172).

⚠️ base-red inherited: #12732 — the four unit shards and Fast Quality Gates fail identically on unrelated PRs cut from the same base.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-09-10 10:13:09 -03:00
committed by GitHub
parent 8ecdd88c15
commit 9debec71ec
1324 changed files with 390361 additions and 38095 deletions

View File

@@ -21,22 +21,31 @@ function mirrorPath(relSource, locale) {
return path.posix.join(DOCS_I18N, locale, relSource);
}
export function buildMirrorBar(relSource, locale, config) {
/**
* `hasMirror(relSource, locale)` — optional existence predicate. A bar must not
* link to a mirror that is not on disk: `docs/guides/I18N.md` is deliberately
* English-only (see `DOCS_EXCLUDED_NAMES` in run-translation.mjs) yet carries
* legacy mirrors for the older locales, so every newly added locale would get a
* dead link. Omit it and every configured locale is listed, as before.
*/
export function buildMirrorBar(relSource, locale, config, { hasMirror } = {}) {
const targetDir = path.posix.dirname(mirrorPath(relSource, locale));
const parts = [`🇺🇸 [English](${path.posix.relative(targetDir, relSource)})`];
for (const entry of config.locales) {
if (entry.code === "en" || entry.code === locale) continue;
if (hasMirror && !hasMirror(relSource, entry.code)) continue;
const peer = path.posix.relative(targetDir, mirrorPath(relSource, entry.code));
parts.push(`${entry.flag} [${entry.code}](${peer})`);
}
return `🌐 **Languages:** ${parts.join(" · ")}`;
}
export function buildSourceBar(relSource, config) {
export function buildSourceBar(relSource, config, { hasMirror } = {}) {
const sourceDir = path.posix.dirname(relSource);
const parts = [`🇺🇸 [English](./${path.posix.basename(relSource)})`];
for (const entry of config.locales) {
if (entry.code === "en") continue;
if (hasMirror && !hasMirror(relSource, entry.code)) continue;
const peer = path.posix.relative(sourceDir, mirrorPath(relSource, entry.code));
parts.push(`${entry.flag} [${entry.native ?? entry.name}](${peer})`);
}

View File

@@ -25,12 +25,15 @@ async function walkMd(dir, out = []) {
export async function syncLanguageBars({ root = ROOT, dryRun = false } = {}) {
const config = JSON.parse(await fs.readFile(path.join(root, "config", "i18n.json"), "utf8"));
const changed = [];
// A bar may only point at a mirror that exists — see buildMirrorBar's note.
const hasMirror = (relSource, locale) =>
existsSync(path.join(root, "docs", "i18n", locale, ...relSource.split("/")));
// 1. English sources under docs/ (excluding docs/i18n).
for (const abs of await walkMd(path.join(root, "docs"))) {
const rel = path.relative(root, abs).split(path.sep).join("/");
if (rel.startsWith("docs/i18n/")) continue;
const text = await fs.readFile(abs, "utf8");
const next = replaceLanguageBar(text, buildSourceBar(rel, config));
const next = replaceLanguageBar(text, buildSourceBar(rel, config, { hasMirror }));
if (next && next !== text) {
changed.push(rel);
if (!dryRun) await fs.writeFile(abs, next, "utf8");
@@ -43,7 +46,10 @@ export async function syncLanguageBars({ root = ROOT, dryRun = false } = {}) {
for (const abs of await walkMd(dir)) {
const relInMirror = path.relative(dir, abs).split(path.sep).join("/");
const text = await fs.readFile(abs, "utf8");
const next = replaceLanguageBar(text, buildMirrorBar(relInMirror, entry.code, config));
const next = replaceLanguageBar(
text,
buildMirrorBar(relInMirror, entry.code, config, { hasMirror })
);
if (next && next !== text) {
changed.push(path.relative(root, abs).split(path.sep).join("/"));
if (!dryRun) await fs.writeFile(abs, next, "utf8");