Files
OmniRoute/scripts/i18n/sync-language-bars.mjs
diegosouzapw c1ea96e03f feat(i18n): add 9 European locales (el hr sr lt et lv sl mt ga)
Batch 1 of the locale-expansion plan: Greek, Croatian, Serbian, Lithuanian,
Estonian, Latvian, Slovenian, Maltese and Irish across every surface —
dashboard catalog, docs mirrors, CLI catalog, README, locale index and the
marketing site. OmniRoute now ships all 24 official EU languages (51 locales).

Also fixes two defects the batch exposed:

- The placeholder-parity gate matched every "{…}" pair, so an ICU plural branch
  body (other {s}) counted as an argument named "s" and any correct plural
  translation was reported as drift. The scanner now follows the ICU grammar.
  Three translations that invented a {count} argument the English source never
  defines were corrected, as was one Irish string that translated the argument
  name itself.
- Language bars linked to mirrors that do not exist: docs/guides/I18N.md is
  English-only by design yet keeps legacy mirrors, so every new locale got a
  dead link. Bars now skip locales without a mirror on disk.
2026-09-08 09:15:01 -03:00

74 lines
3.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Rewrites every `🌐 **Languages:** …` bar from config/i18n.json:
* - English sources (root MDs + docs/**.md that already carry a bar) → buildSourceBar
* - mirrors under docs/i18n/<locale>/ → buildMirrorBar
* Never inserts a bar where there is none. Idempotent. `--dry-run` lists files.
*/
import { promises as fs, existsSync } from "node:fs";
import path from "node:path";
import process from "node:process";
import { fileURLToPath, pathToFileURL } from "node:url";
import { buildMirrorBar, buildSourceBar, replaceLanguageBar } from "./lib/language-bar.mjs";
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
async function walkMd(dir, out = []) {
for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
const abs = path.join(dir, entry.name);
if (entry.isDirectory()) await walkMd(abs, out);
else if (entry.name.endsWith(".md") || entry.name === "llm.txt") out.push(abs);
}
return 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, { hasMirror }));
if (next && next !== text) {
changed.push(rel);
if (!dryRun) await fs.writeFile(abs, next, "utf8");
}
}
// 2. Mirrors.
for (const entry of config.locales) {
const dir = path.join(root, "docs", "i18n", entry.code);
if (!existsSync(dir)) continue;
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, { hasMirror })
);
if (next && next !== text) {
changed.push(path.relative(root, abs).split(path.sep).join("/"));
if (!dryRun) await fs.writeFile(abs, next, "utf8");
}
}
}
return changed;
}
const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
if (isDirectRun) {
const dryRun = process.argv.includes("--dry-run");
syncLanguageBars({ dryRun })
.then((changed) =>
console.log(`[i18n-bars] ${dryRun ? "would update" : "updated"} ${changed.length} file(s)`)
)
.catch((err) => {
console.error("[i18n-bars] ERROR", err?.stack || err?.message || String(err));
process.exit(1);
});
}